using Microsoft.AspNetCore.DataProtection;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
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.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.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;

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

/// <summary>
/// Regression guard for the create-missing site/connection apply path on a REAL
/// relational provider (SQLite). Every other importer integration test runs on the
/// EF in-memory provider, which assigns surrogate ids eagerly on <c>AddAsync</c> —
/// masking that a create-missing <c>Site</c> still has <c>Id == 0</c> until the
/// context is flushed. <c>ApplyDataConnectionsAsync</c> stamps
/// <c>DataConnection.SiteId</c> as a raw scalar (no EF navigation, so no fix-up),
/// so without a flush between the site pass and the connection pass the insert
/// fails the FK on a relational provider (live failure 2026-08-01: SQL Server
/// error 547, <c>FK_DataConnections_Sites_SiteId</c>, on a cross-environment
/// bundle import into an empty target).
/// </summary>
public sealed class CreateMissingSiteRelationalTests : IDisposable
{
    private readonly SqliteConnection _connection;
    private readonly ServiceProvider _provider;

    public CreateMissingSiteRelationalTests()
    {
        // One open in-memory SQLite connection for the fixture's lifetime — the
        // database lives exactly as long as the connection.
        _connection = new SqliteConnection("DataSource=:memory:");
        _connection.Open();

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

        services.AddDbContext<ScadaBridgeDbContext>(opts => opts.UseSqlite(_connection));

        // Secret-bearing columns require the encrypting two-arg ctor — same wiring
        // as RoundTripTests / BundleImporterRollbackFailureTests.
        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<IAuditCorrelationContext, AuditCorrelationContext>();
        services.AddScoped<IAuditService, AuditService>();
        services.AddTransport();

        _provider = services.BuildServiceProvider();

        using var scope = _provider.CreateScope();
        scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>().Database.EnsureCreated();
    }

    public void Dispose()
    {
        _provider.Dispose();
        _connection.Dispose();
    }

    [Fact]
    public async Task Import_create_missing_site_and_connection_wires_the_materialised_site_id()
    {
        // ---- Seed a source site + connection, export the site closure. ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var site = new Site("Plant 1", "plant-1")
            {
                Description = "source plant",
                NodeAAddress = "akka://site@10.0.0.1:2552",
                NodeBAddress = "akka://site@10.0.0.2:2552",
                GrpcNodeAAddress = "10.0.0.1:8083",
                GrpcNodeBAddress = "10.0.0.2:8083",
            };
            ctx.Sites.Add(site);
            await ctx.SaveChangesAsync();
            ctx.DataConnections.Add(new DataConnection("OpcUaPrimary", "OpcUa", site.Id)
            {
                PrimaryConfiguration = "{\"endpoint\":\"opc.tcp://primary\"}",
                FailoverRetryCount = 5,
            });
            await ctx.SaveChangesAsync();
        }

        Guid sessionId;
        await using (var scope = _provider.CreateAsyncScope())
        {
            var exporter = scope.ServiceProvider.GetRequiredService<IBundleExporter>();
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var siteIds = await ctx.Sites.Select(s => s.Id).ToListAsync();
            var bundleStream = await exporter.ExportAsync(
                new ExportSelection(
                    TemplateIds: Array.Empty<int>(),
                    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: true,
                    SiteIds: siteIds),
                user: "alice", sourceEnvironment: "dev",
                passphrase: null, cancellationToken: CancellationToken.None);

            using var ms = new MemoryStream();
            await bundleStream.CopyToAsync(ms);
            ms.Position = 0;
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            var session = await importer.LoadAsync(ms, passphrase: null);
            sessionId = session.SessionId;
        }

        // ---- Wipe the target so the apply exercises CreateNew on both. ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            ctx.DataConnections.RemoveRange(ctx.DataConnections);
            ctx.Sites.RemoveRange(ctx.Sites);
            await ctx.SaveChangesAsync();
        }

        // ---- Apply with create-missing mappings (the CLI's --create-missing-*). ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            var nameMap = new BundleNameMap(
                Sites: [new SiteMapping("plant-1", MappingAction.CreateNew, null)],
                Connections: [new ConnectionMapping("plant-1", "OpcUaPrimary", MappingAction.CreateNew, null)]);
            await importer.ApplyAsync(
                sessionId, Array.Empty<ImportResolution>(), user: "bob",
                ct: CancellationToken.None, nameMap: nameMap);
        }

        // ---- The created connection must reference the created site's REAL id. ----
        await using (var verify = _provider.CreateAsyncScope())
        {
            var ctx = verify.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var site = Assert.Single(await ctx.Sites.ToListAsync());
            var conn = Assert.Single(await ctx.DataConnections.ToListAsync());
            Assert.True(site.Id > 0, "created site id was never materialised");
            Assert.Equal(site.Id, conn.SiteId);
        }
    }
}
