using Microsoft.Extensions.Logging;
using ZB.MOM.WW.HistorianGateway.Client;
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
using ZB.MOM.WW.OtOpcUa.Runtime.Historian;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;

/// <summary>
///     Concrete <see cref="IHistorianGatewayClient"/> backed by the published
///     <see cref="HistorianGatewayClient"/> package client. Each seam method forwards directly to the
///     matching client wrapper — both sides speak the same generated <c>historian_gateway.v1</c> proto
///     types, so no shape translation happens here. The package client's typed exception hierarchy
///     (<c>HistorianGatewayUnavailableException</c> et al.) is allowed to surface unchanged; the
///     <see cref="GatewayHistorianDataSource"/> records it as a health failure and the node manager
///     turns it into a Bad HistoryRead result.
/// </summary>
/// <remarks>
///     <para>
///         <b>Lazy channel.</b> <see cref="Create"/> calls <see cref="HistorianGatewayClient.Create"/>,
///         which constructs a <c>GrpcChannel</c> over a <c>SocketsHttpHandler</c> without opening a
///         connection — the first RPC dials. Constructing the adapter therefore performs no network I/O,
///         which the offline seam tests rely on (they build from bogus endpoints and must not connect).
///     </para>
/// </remarks>
public sealed class HistorianGatewayClientAdapter : IHistorianGatewayClient, IDisposable
{
    private readonly HistorianGatewayClient _inner;

    private HistorianGatewayClientAdapter(HistorianGatewayClient inner) => _inner = inner;

    /// <summary>
    ///     Builds an adapter over a freshly created package client mapped from the bound
    ///     <see cref="ServerHistorianOptions"/>. No connection is opened (lazy channel).
    /// </summary>
    /// <param name="options">The bound <c>ServerHistorian</c> configuration (endpoint, key, TLS posture).</param>
    /// <param name="loggerFactory">Logger factory threaded into the package client's channel diagnostics.</param>
    /// <returns>A ready-to-use adapter whose underlying channel has not yet dialed the gateway.</returns>
    /// <exception cref="InvalidOperationException">
    ///     <see cref="ServerHistorianOptions.Endpoint"/> is empty or not an absolute <c>http(s)</c> URI.
    ///     Defense in depth (archreview 06/S-11): the Host's <c>ServerHistorianOptionsValidator</c> fails
    ///     these configs at startup, but any caller that bypasses <c>ValidateOnStart</c> (tests, the live
    ///     fixture, future non-Host wiring) still gets a named, config-key-carrying error here instead of
    ///     the raw <see cref="UriFormatException"/> the bare <c>new Uri(...)</c> ctor throws.
    /// </exception>
    public static HistorianGatewayClientAdapter Create(ServerHistorianOptions options, ILoggerFactory loggerFactory)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(loggerFactory);

        // Fail fast with a named, actionable error rather than letting `new Uri("")` throw a raw
        // UriFormatException deep in the factory (archreview 06/S-11). The Endpoint is not a secret,
        // so it is safe to echo; the ApiKey is never surfaced.
        if (!Uri.TryCreate(options.Endpoint, UriKind.Absolute, out var endpointUri)
            || (endpointUri.Scheme != Uri.UriSchemeHttp && endpointUri.Scheme != Uri.UriSchemeHttps))
        {
            throw new InvalidOperationException(
                $"ServerHistorian:Endpoint must be an absolute http(s) URI (e.g. https://host:5222); got '{options.Endpoint}'.");
        }

        // TLS-only options must stay at their defaults on a plaintext h2c connection: the client rejects
        // each of them outright when UseTls=false ("<name> is a TLS-only option and requires
        // UseTls=true"). Forwarding them unconditionally made h2c UNREACHABLE — AllowUntrustedServer-
        // Certificate defaults to false, so RequireCertificateValidation was always sent as true and
        // every http:// deployment crashed at startup, even though the scheme is documented as the
        // supported way to select h2c. There is no certificate to have a posture about here.
        var clientOptions = new HistorianGatewayClientOptions
        {
            Endpoint = endpointUri,
            ApiKey = options.ApiKey,
            UseTls = options.UseTls,
            CaCertificatePath = options.UseTls ? options.CaCertificatePath : null,
            // INVERTED mapping (TLS only): ServerHistorianOptions.AllowUntrustedServerCertificate
            // (opt-in to accept a self-signed cert) is the negation of the client's
            // RequireCertificateValidation. Allowing an untrusted cert == not requiring validation; a
            // pinned CaCertificatePath always verifies.
            RequireCertificateValidation = options.UseTls && !options.AllowUntrustedServerCertificate,
            DefaultCallTimeout = options.CallTimeout,
            LoggerFactory = loggerFactory,
        };

        return new HistorianGatewayClientAdapter(HistorianGatewayClient.Create(clientOptions));
    }

    /// <inheritdoc />
    public IAsyncEnumerable<HistorianSample> ReadRawAsync(
        string tag, DateTime startUtc, DateTime endUtc, int maxValues, CancellationToken ct) =>
        _inner.ReadRawAsync(tag, startUtc, endUtc, maxValues, ct);

    /// <inheritdoc />
    public IAsyncEnumerable<HistorianAggregateSample> ReadAggregateAsync(
        string tag, DateTime startUtc, DateTime endUtc, RetrievalMode mode, TimeSpan interval, CancellationToken ct) =>
        _inner.ReadAggregateAsync(tag, startUtc, endUtc, mode, interval, ct);

    /// <inheritdoc />
    public Task<IReadOnlyList<HistorianSample>> ReadAtTimeAsync(
        string tag, IReadOnlyList<DateTime> timestampsUtc, CancellationToken ct) =>
        _inner.ReadAtTimeAsync(tag, timestampsUtc, ct);

    /// <inheritdoc />
    public IAsyncEnumerable<HistorianEvent> ReadEventsAsync(
        string? sourceName, DateTime startUtc, DateTime endUtc, int maxEvents, CancellationToken ct)
    {
        HistorianEventFilter? filter = sourceName is null
            ? null
            : new HistorianEventFilter
            {
                PropertyName = "Source_Object",
                Comparison = HistorianEventComparison.Equal,
                Value = sourceName,
            };

        return _inner.ReadEventsAsync(startUtc, endUtc, filter, ct);
    }

    /// <inheritdoc />
    public Task<WriteAck> WriteLiveValuesAsync(
        string tag, IReadOnlyList<HistorianLiveValue> values, CancellationToken ct) =>
        _inner.WriteLiveValuesAsync(tag, values, ct);

    /// <inheritdoc />
    public Task<WriteAck> SendEventAsync(HistorianEvent evt, CancellationToken ct) =>
        _inner.SendEventAsync(evt, ct);

    /// <inheritdoc />
    public Task<TagOperationResults> EnsureTagsAsync(
        IReadOnlyList<HistorianTagDefinition> definitions, CancellationToken ct) =>
        _inner.EnsureTagsAsync(definitions, ct);

    /// <inheritdoc />
    public Task<bool> ProbeAsync(CancellationToken ct) => _inner.ProbeAsync(ct);

    /// <inheritdoc />
    public Task<ConnectionStatus> GetConnectionStatusAsync(CancellationToken ct) =>
        _inner.GetConnectionStatusAsync(ct);

    /// <summary>Disposes the underlying package client (and its channel). Prefer <see cref="DisposeAsync"/>.</summary>
    public void Dispose() => _inner.Dispose();

    /// <summary>Asynchronously disposes the underlying package client (and its channel).</summary>
    /// <returns>A task that completes when the client has been disposed.</returns>
    public ValueTask DisposeAsync() => _inner.DisposeAsync();
}
