using System.Text.Json;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;

namespace ZB.MOM.WW.OtOpcUa.Server;

/// <summary>
///     Phase 6.1 Stream D consumption hook — bootstraps the node's current generation through
///     the <see cref="ResilientConfigReader"/> pipeline + writes every successful central-DB
///     read into the <see cref="GenerationSealedCache"/> so the next cache-miss path has a
///     sealed snapshot to fall back to.
/// </summary>
/// <remarks>
///     <para>Alongside the original <see cref="NodeBootstrap"/> (which uses the single-file
///     <see cref="ILocalConfigCache"/>). Program.cs can switch to this one once operators are
///     ready for the generation-sealed semantics. The original stays for backward compat
///     with the three integration tests that construct <see cref="NodeBootstrap"/> directly.</para>
///
///     <para>Closes release blocker #2 in <c>docs/v2/v2-release-readiness.md</c> — the
///     generation-sealed cache + resilient reader + stale-config flag ship as unit-tested
///     primitives in PR #81 but no production path consumed them until this wrapper.</para>
/// </remarks>
public sealed class SealedBootstrap
{
    private readonly NodeOptions _options;
    private readonly GenerationSealedCache _cache;
    private readonly ResilientConfigReader _reader;
    private readonly StaleConfigFlag _staleFlag;
    private readonly ILogger<SealedBootstrap> _logger;

    public SealedBootstrap(
        NodeOptions options,
        GenerationSealedCache cache,
        ResilientConfigReader reader,
        StaleConfigFlag staleFlag,
        ILogger<SealedBootstrap> logger)
    {
        _options = options;
        _cache = cache;
        _reader = reader;
        _staleFlag = staleFlag;
        _logger = logger;
    }

    /// <summary>
    ///     Resolve the current generation for this node. Routes the central-DB fetch through
    ///     <see cref="ResilientConfigReader"/> (timeout → retry → fallback-to-cache) + seals a
    ///     fresh snapshot on every successful DB read so a future cache-miss has something to
    ///     serve.
    /// </summary>
    public async Task<BootstrapResult> LoadCurrentGenerationAsync(CancellationToken ct)
    {
        return await _reader.ReadAsync(
            _options.ClusterId,
            centralFetch: async innerCt => await FetchFromCentralAsync(innerCt).ConfigureAwait(false),
            fromSnapshot: snap => BootstrapResult.FromCache(snap.GenerationId),
            ct).ConfigureAwait(false);
    }

    private async ValueTask<BootstrapResult> FetchFromCentralAsync(CancellationToken ct)
    {
        await using var conn = new SqlConnection(_options.ConfigDbConnectionString);
        await conn.OpenAsync(ct).ConfigureAwait(false);

        await using var cmd = conn.CreateCommand();
        cmd.CommandText = "EXEC dbo.sp_GetCurrentGenerationForCluster @NodeId=@n, @ClusterId=@c";
        cmd.Parameters.AddWithValue("@n", _options.NodeId);
        cmd.Parameters.AddWithValue("@c", _options.ClusterId);

        await using var reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);
        if (!await reader.ReadAsync(ct).ConfigureAwait(false))
        {
            _logger.LogWarning("Cluster {Cluster} has no Published generation yet", _options.ClusterId);
            return BootstrapResult.EmptyFromDb();
        }

        var generationId = reader.GetInt64(0);
        _logger.LogInformation("Bootstrapped from central DB: generation {GenerationId}; sealing snapshot", generationId);

        // Seal a minimal snapshot with the generation pointer. A richer snapshot that carries
        // the full sp_GetGenerationContent payload lands when the bootstrap flow grows to
        // consume the content during offline operation (separate follow-up — see decision #148
        // and phase-6-1 Stream D.3). The pointer alone is enough for the fallback path to
        // surface the last-known-good generation id + flip UsingStaleConfig.
        await _cache.SealAsync(new GenerationSnapshot
        {
            ClusterId = _options.ClusterId,
            GenerationId = generationId,
            CachedAt = DateTime.UtcNow,
            PayloadJson = JsonSerializer.Serialize(new { generationId, source = "sp_GetCurrentGenerationForCluster" }),
        }, ct).ConfigureAwait(false);

        // StaleConfigFlag bookkeeping: ResilientConfigReader.MarkFresh on the returning call
        // path; we're on the fresh branch so we don't touch the flag here.
        _ = _staleFlag; // held so the field isn't flagged unused

        return BootstrapResult.FromDb(generationId);
    }
}
