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.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.Transport.Serialization;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;

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

/// <summary>
/// #05-T3: a template script's <see cref="TemplateScript.MinTimeBetweenRuns"/> and
/// <see cref="TemplateScript.ExecutionTimeoutSeconds"/> must survive an export→import
/// round-trip. The exporter serializes both fields (EntitySerializer) and the DTO
/// carries them; before this fix the importer's <c>BuildTemplate</c> (Add) and
/// <c>SyncTemplateScriptsAsync</c> (Overwrite) rebuild dropped them, silently resetting
/// the persisted script to schema defaults. Same in-memory host pattern as
/// <see cref="BundleImporterApplyTests"/>.
/// </summary>
public sealed class TemplateScriptFidelityTests : IDisposable
{
    private readonly ServiceProvider _provider;

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

        var dbName = $"TemplateScriptFidelityTests_{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> ExportAndLoadAsync()
    {
        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;
    }

    [Fact]
    public async Task Add_preserves_MinTimeBetweenRuns_and_ExecutionTimeoutSeconds()
    {
        // Arrange: seed a template whose script carries non-default cadence + timeout.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "fresh" };
            t.Scripts.Add(new TemplateScript("cadence", "return 1;")
            {
                MinTimeBetweenRuns = TimeSpan.FromSeconds(30),
                ExecutionTimeoutSeconds = 42,
            });
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAndLoadAsync();

        // Wipe so the import exercises the Add path.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            ctx.Templates.RemoveRange(ctx.Templates);
            await ctx.SaveChangesAsync();
        }

        // Act
        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");
        }

        // Assert: both fields survived the rebuild.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var script = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .SelectMany(t => t.Scripts)
                .SingleAsync(s => s.Name == "cadence");
            Assert.Equal(TimeSpan.FromSeconds(30), script.MinTimeBetweenRuns);
            Assert.Equal(42, script.ExecutionTimeoutSeconds);
        }
    }

    [Fact]
    public async Task Overwrite_updates_ExecutionTimeoutSeconds_only_diff_and_emits_audit()
    {
        // Arrange: seed script with ExecutionTimeoutSeconds=42; export; then mutate the
        // target script so it differs from the bundle ONLY in ExecutionTimeoutSeconds.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "same" };
            t.Scripts.Add(new TemplateScript("cadence", "return 1;")
            {
                MinTimeBetweenRuns = TimeSpan.FromSeconds(30),
                ExecutionTimeoutSeconds = 42,
            });
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAndLoadAsync();
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var script = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .SelectMany(t => t.Scripts)
                .SingleAsync(s => s.Name == "cadence");
            script.ExecutionTimeoutSeconds = 10; // now the only field that differs from the bundle
            await ctx.SaveChangesAsync();
        }

        // Act
        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");
        }

        // Assert: field restored to the bundle value AND a TemplateScriptUpdated audit row emitted.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var script = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .SelectMany(t => t.Scripts)
                .SingleAsync(s => s.Name == "cadence");
            Assert.Equal(42, script.ExecutionTimeoutSeconds);

            Assert.True(await ctx.AuditLogEntries.AnyAsync(a =>
                a.Action == "TemplateScriptUpdated" && a.EntityName == "Pump.cadence"));
        }
    }

    [Fact]
    public async Task Add_preserves_LockedInDerived_on_attribute_alarm_script()
    {
        // #05-T4: LockedInDerived must survive export→import on all three child kinds.
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var t = new Template("Pump") { Description = "fresh" };
            t.Attributes.Add(new TemplateAttribute("Pressure")
            {
                DataType = DataType.Double,
                LockedInDerived = true,
            });
            t.Alarms.Add(new TemplateAlarm("High")
            {
                TriggerType = AlarmTriggerType.RangeViolation,
                LockedInDerived = true,
            });
            t.Scripts.Add(new TemplateScript("cadence", "return 1;")
            {
                LockedInDerived = true,
            });
            ctx.Templates.Add(t);
            await ctx.SaveChangesAsync();
        }
        var sessionId = await ExportAndLoadAsync();

        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 t = await ctx.Templates
                .Where(t => t.Name == "Pump")
                .Include(t => t.Attributes)
                .Include(t => t.Alarms)
                .Include(t => t.Scripts)
                .SingleAsync();
            Assert.True(t.Attributes.Single(a => a.Name == "Pressure").LockedInDerived);
            Assert.True(t.Alarms.Single(a => a.Name == "High").LockedInDerived);
            Assert.True(t.Scripts.Single(s => s.Name == "cadence").LockedInDerived);
        }
    }
}
