using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;

namespace ZB.MOM.WW.OtOpcUa.Server;

/// <summary>
///     Task #248 — bridges the gap surfaced by the Phase 7 live smoke (#240) where
///     <c>DriverInstance</c> rows in the central config DB had no path to materialise
///     as live <see cref="Core.Abstractions.IDriver"/> instances in <see cref="DriverHost"/>.
///     Called from <c>OpcUaServerService.ExecuteAsync</c> after the bootstrap loads
///     the published generation, before address-space build.
/// </summary>
/// <remarks>
///     <para>
///         Per row: looks up the <c>DriverType</c> string in
///         <see cref="DriverFactoryRegistry"/>, calls the factory with the row's
///         <c>DriverInstanceId</c> + <c>DriverConfig</c> JSON to construct an
///         <see cref="Core.Abstractions.IDriver"/>, then registers via
///         <see cref="DriverHost.RegisterAsync"/> which invokes <c>InitializeAsync</c>
///         under the host's lifecycle semantics.
///     </para>
///     <para>
///         Unknown <c>DriverType</c> = factory not registered = log a warning and skip.
///         Per plan decision #12 (driver isolation), failure to construct or initialize
///         one driver doesn't prevent the rest from coming up — the Server keeps serving
///         the others' subtrees + the operator can fix the misconfigured row + republish
///         to retry.
///     </para>
/// </remarks>
public sealed class DriverInstanceBootstrapper(
    DriverFactoryRegistry factories,
    DriverHost driverHost,
    IServiceScopeFactory scopeFactory,
    ILogger<DriverInstanceBootstrapper> logger)
{
    public async Task<int> RegisterDriversFromGenerationAsync(long generationId, CancellationToken ct)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<OtOpcUaConfigDbContext>();

        var rows = await db.DriverInstances.AsNoTracking()
            .Where(d => d.GenerationId == generationId && d.Enabled)
            .ToListAsync(ct).ConfigureAwait(false);

        var registered = 0;
        var skippedUnknownType = 0;
        var failedInit = 0;

        foreach (var row in rows)
        {
            var factory = factories.TryGet(row.DriverType);
            if (factory is null)
            {
                logger.LogWarning(
                    "DriverInstance {Id} skipped — DriverType '{Type}' has no registered factory (known: {Known})",
                    row.DriverInstanceId, row.DriverType, string.Join(",", factories.RegisteredTypes));
                skippedUnknownType++;
                continue;
            }

            try
            {
                var driver = factory(row.DriverInstanceId, row.DriverConfig);
                await driverHost.RegisterAsync(driver, row.DriverConfig, ct).ConfigureAwait(false);
                registered++;
                logger.LogInformation(
                    "DriverInstance {Id} ({Type}) registered + initialized", row.DriverInstanceId, row.DriverType);
            }
            catch (Exception ex)
            {
                // Plan decision #12 — driver isolation. Log + continue so one bad row
                // doesn't deny the OPC UA endpoint to the rest of the fleet.
                logger.LogError(ex,
                    "DriverInstance {Id} ({Type}) failed to initialize — driver state will reflect Faulted; operator can republish to retry",
                    row.DriverInstanceId, row.DriverType);
                failedInit++;
            }
        }

        logger.LogInformation(
            "DriverInstanceBootstrapper: gen={Gen} registered={Registered} skippedUnknownType={Skipped} failedInit={Failed}",
            generationId, registered, skippedUnknownType, failedInit);
        return registered;
    }
}
