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

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

/// <summary>
/// Integration tests for Area-by-name transport (Task 7): a source instance
/// assigned to an <see cref="Area"/> under its site must carry that area's NAME
/// through the exported bundle so the importer's <c>ResolveOrCreateAreaIdAsync</c>
/// machinery can resolve-or-create the equivalent area under the target site. This
/// closes the half-shipped Area transport — the importer's GetOrCreate path was
/// already built + tested, but the exporter hardcoded <c>AreaName: null</c>.
/// <para>
/// Reuses the in-memory host pattern from <c>SiteInstanceImportTests</c>: real
/// repositories, real EF in-memory provider, real Transport pipeline.
/// </para>
/// </summary>
public sealed class AreaTransportTests : IDisposable
{
    private readonly ServiceProvider _provider;

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

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

        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();
    }

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

    [Fact]
    public async Task Instance_Area_travels_by_name_and_resolves_under_target_site()
    {
        // ---- Seed a site + instance assigned to an Area "Line-1" ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();

            var template = new Template("Pump") { Description = "pump tpl" };
            ctx.Templates.Add(template);

            var site = new Site("Plant 1", "plant-1");
            ctx.Sites.Add(site);
            await ctx.SaveChangesAsync();

            var area = new Area("Line-1") { SiteId = site.Id };
            ctx.Areas.Add(area);
            await ctx.SaveChangesAsync();

            var instance = new Instance("Pump-01")
            {
                TemplateId = template.Id,
                SiteId = site.Id,
                AreaId = area.Id,
                State = InstanceState.Enabled,
            };
            ctx.Instances.Add(instance);
            await ctx.SaveChangesAsync();
        }

        // ---- Export the whole site closure ----
        Stream bundleStream;
        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 selection = 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);
            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;

        // ---- Wipe the source rows so the import creates a fresh target ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            ctx.Instances.RemoveRange(ctx.Instances);
            ctx.Areas.RemoveRange(ctx.Areas);
            ctx.Sites.RemoveRange(ctx.Sites);
            ctx.Templates.RemoveRange(ctx.Templates);
            await ctx.SaveChangesAsync();
            // The template must exist for the instance to resolve its TemplateId.
            ctx.Templates.Add(new Template("Pump") { Description = "pump tpl" });
            await ctx.SaveChangesAsync();
        }

        // ---- Import with a CreateNew site mapping ----
        Guid sessionId;
        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            var session = await importer.LoadAsync(ms, passphrase: null);
            sessionId = session.SessionId;
        }

        var nameMap = new BundleNameMap(
            Sites: new[] { new SiteMapping("plant-1", MappingAction.CreateNew, null) },
            Connections: Array.Empty<ConnectionMapping>());

        await using (var scope = _provider.CreateAsyncScope())
        {
            var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
            await importer.ApplyAsync(
                sessionId,
                new List<ImportResolution>
                {
                    new("Template", "Pump", ResolutionAction.Skip, null),
                    new("Site", "plant-1", ResolutionAction.Add, null),
                    new("Instance", "Pump-01", ResolutionAction.Add, null),
                },
                user: "bob",
                ct: CancellationToken.None,
                nameMap: nameMap);
        }

        // ---- Assert: the imported instance's AreaId resolves to an Area named
        //      "Line-1" under the target site (NOT null). ----
        await using (var scope = _provider.CreateAsyncScope())
        {
            var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
            var site = await ctx.Sites.SingleAsync(s => s.SiteIdentifier == "plant-1");
            var inst = await ctx.Instances.SingleAsync(i => i.UniqueName == "Pump-01");

            Assert.NotNull(inst.AreaId);
            var area = await ctx.Areas.SingleAsync(a => a.Id == inst.AreaId);
            Assert.Equal("Line-1", area.Name);
            Assert.Equal(site.Id, area.SiteId);
        }
    }
}
