using System.Collections.Generic;
using OpenTelemetry.Resources;

namespace ZB.MOM.WW.Telemetry;

/// <summary>
/// Builds the shared OpenTelemetry ResourceBuilder from <see cref="ZbTelemetryOptions"/>.
/// Used internally by <c>AddZbTelemetry</c> so metrics, traces, and logs carry an identical
/// Resource. Exposed for tests and custom pipelines.
/// </summary>
public static class ZbResource
{
    /// <summary>
    /// Deterministic, process-stable service instance identifier. Formatted as
    /// <c>MachineName:ProcessId</c> so that every signal (metrics, traces, logs) from the same
    /// process carries the exact same <c>service.instance.id</c>, enabling cross-signal
    /// correlation without a random GUID that changes on each startup.
    /// </summary>
    public static string InstanceId =>
        $"{System.Environment.MachineName}:{System.Environment.ProcessId}";

    /// <summary>
    /// Returns a <see cref="ResourceBuilder"/> pre-populated with <c>service.name</c>,
    /// <c>service.namespace</c>, <c>service.version</c>, <c>service.instance.id</c>,
    /// <c>site.id</c>, <c>node.role</c>, and <c>host.name</c> (always
    /// <see cref="System.Environment.MachineName"/>). Attributes with null values are omitted
    /// from the Resource.
    /// </summary>
    /// <param name="options">The telemetry options describing the service identity.</param>
    public static ResourceBuilder Build(ZbTelemetryOptions options) =>
        Configure(ResourceBuilder.CreateDefault(), options);

    /// <summary>
    /// The single source of truth for the shared ZB.MOM.WW Resource attribute set. Every consumer
    /// of the Resource — the OTel SDK metrics/traces pipeline (<see cref="Configure"/>) and the
    /// Serilog OTLP log sink — derives its attributes from this one map, so logs can never drift
    /// from metrics/traces. Required attributes (<c>service.name</c>, <c>service.namespace</c>,
    /// <c>service.instance.id</c>, <c>host.name</c>) are always present; optional ones
    /// (<c>service.version</c>, <c>site.id</c>, <c>node.role</c>) are included only when the
    /// corresponding option is non-null/non-empty, matching the Resource's omission rules.
    /// </summary>
    /// <param name="options">The telemetry options describing the service identity.</param>
    /// <returns>The canonical attribute map carried by all three signals.</returns>
    public static IReadOnlyDictionary<string, object> BuildAttributes(ZbTelemetryOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);

        var attributes = new Dictionary<string, object>(StringComparer.Ordinal)
        {
            ["service.name"] = options.ServiceName,
            ["service.namespace"] = options.ServiceNamespace,
            ["service.instance.id"] = InstanceId,
            ["host.name"] = System.Environment.MachineName,
        };

        if (!string.IsNullOrEmpty(options.ServiceVersion))
        {
            attributes["service.version"] = options.ServiceVersion;
        }

        if (!string.IsNullOrEmpty(options.SiteId))
        {
            attributes["site.id"] = options.SiteId;
        }

        if (!string.IsNullOrEmpty(options.NodeRole))
        {
            attributes["node.role"] = options.NodeRole;
        }

        return attributes;
    }

    /// <summary>
    /// Applies the shared ZB.MOM.WW Resource attributes to an existing <see cref="ResourceBuilder"/>.
    /// Internal seam so the <c>AddZbTelemetry</c> pipeline produces a Resource identical to
    /// <see cref="Build"/>. Derives every attribute from <see cref="BuildAttributes"/> — the same
    /// canonical map the Serilog OTLP log sink uses — so all three signals agree.
    /// </summary>
    internal static ResourceBuilder Configure(ResourceBuilder builder, ZbTelemetryOptions options)
    {
        var attributes = BuildAttributes(options);
        builder.AddAttributes(attributes);
        return builder;
    }
}
