// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.

package tracer

import (
	"fmt"
	"strings"

	"github.com/DataDog/dd-trace-go/v2/internal/env"
	"github.com/DataDog/dd-trace-go/v2/internal/log"
	"github.com/DataDog/dd-trace-go/v2/internal/telemetry"
)

var additionalConfigs []telemetry.Configuration

func reportTelemetryOnAppStarted(c telemetry.Configuration) {
	additionalConfigs = append(additionalConfigs, c)
}

// startTelemetry starts the global instrumentation telemetry client with tracer data
// unless instrumentation telemetry is disabled via the DD_INSTRUMENTATION_TELEMETRY_ENABLED
// env var.
// If the telemetry client has already been started by the profiler, then
// an app-product-change event is sent with appsec information and an app-client-configuration-change
// event is sent with tracer config data.
// Note that the tracer is not considered as a standalone product by telemetry so we cannot send
// an app-product-change event for the tracer.
// TODO (APMAPI-1771): This function should be deleted once config migration is complete
func startTelemetry(c *config) telemetry.Client {
	if telemetry.Disabled() {
		// Do not do extra work populating config data if instrumentation telemetry is disabled.
		return nil
	}

	telemetry.ProductStarted(telemetry.NamespaceTracers)
	// Read enabled value and origin atomically to prevent TOCTOU bugs
	traceEnabled, traceEnabledOrigin := c.enabled.getCurrentAndOrigin()
	telemetryConfigs := []telemetry.Configuration{
		{Name: "agent_feature_drop_p0s", Value: c.agent.DropP0s},
		{Name: "stats_computation_enabled", Value: c.canComputeStats()},
		{Name: "dogstatsd_port", Value: c.agent.StatsdPort},
		{Name: "lambda_mode", Value: c.internalConfig.LogToStdout()},
		{Name: "send_retries", Value: c.sendRetries},
		{Name: "retry_interval", Value: c.internalConfig.RetryInterval()},
		{Name: "trace_startup_logs_enabled", Value: c.internalConfig.LogStartup()},
		{Name: "service", Value: c.serviceName},
		{Name: "universal_version", Value: c.universalVersion},
		{Name: "env", Value: c.internalConfig.Env()},
		{Name: "version", Value: c.internalConfig.Version()},
		{Name: "trace_agent_url", Value: c.agentURL.String()},
		{Name: "agent_hostname", Value: c.internalConfig.Hostname()},
		{Name: "runtime_metrics_v2_enabled", Value: c.internalConfig.RuntimeMetricsV2Enabled()},
		{Name: "dogstatsd_addr", Value: c.dogstatsdAddr},
		{Name: "profiling_endpoints_enabled", Value: c.internalConfig.ProfilerEndpoints()},
		{Name: "debug_stack_enabled", Value: c.internalConfig.DebugStack()},
		{Name: "profiling_hotspots_enabled", Value: c.internalConfig.ProfilerHotspotsEnabled()},
		{Name: "trace_span_attribute_schema", Value: c.spanAttributeSchemaVersion},
		{Name: "trace_peer_service_defaults_enabled", Value: c.peerServiceDefaultsEnabled},
		{Name: "orchestrion_enabled", Value: c.orchestrionCfg.Enabled, Origin: telemetry.OriginCode},
		{Name: "trace_enabled", Value: traceEnabled, Origin: traceEnabledOrigin},
		{Name: "trace_log_directory", Value: c.internalConfig.LogDirectory()},
		c.traceSampleRate.toTelemetry(),
		c.headerAsTags.toTelemetry(),
		c.globalTags.toTelemetry(),
		c.traceSampleRules.toTelemetry(),
		{Name: "span_sample_rules", Value: c.spanRules},
	}
	var peerServiceMapping []string
	for key, value := range c.peerServiceMappings {
		peerServiceMapping = append(peerServiceMapping, fmt.Sprintf("%s:%s", key, value))
	}
	telemetryConfigs = append(telemetryConfigs,
		telemetry.Configuration{Name: "trace_peer_service_mapping", Value: strings.Join(peerServiceMapping, ",")})

	if chained, ok := c.propagator.(*chainedPropagator); ok {
		telemetryConfigs = append(telemetryConfigs,
			telemetry.Configuration{Name: "trace_propagation_style_inject", Value: chained.injectorNames})
		telemetryConfigs = append(telemetryConfigs,
			telemetry.Configuration{Name: "trace_propagation_style_extract", Value: chained.extractorsNames})
	}
	for k, v := range c.internalConfig.FeatureFlags() {
		telemetryConfigs = append(telemetryConfigs, telemetry.Configuration{Name: k, Value: v})
	}
	for k, v := range c.internalConfig.ServiceMappings() {
		telemetryConfigs = append(telemetryConfigs, telemetry.Configuration{Name: "service_mapping_" + k, Value: v})
	}
	for k, v := range c.globalTags.get() {
		telemetryConfigs = append(telemetryConfigs, telemetry.Configuration{Name: "global_tag_" + k, Value: v})
	}
	rules := append(c.spanRules, c.traceRules...)
	for _, rule := range rules {
		var service string
		var name string
		if rule.Service != nil {
			service = rule.Service.String()
		}
		if rule.Name != nil {
			name = rule.Name.String()
		}
		telemetryConfigs = append(telemetryConfigs,
			telemetry.Configuration{Name: fmt.Sprintf("sr_%s_(%s)_(%s)", rule.ruleType.String(), service, name),
				Value: fmt.Sprintf("rate:%f_maxPerSecond:%f", rule.Rate, rule.MaxPerSecond)})
	}
	if c.orchestrionCfg.Enabled {
		telemetryConfigs = append(telemetryConfigs, telemetry.Configuration{Name: "orchestrion_version", Value: c.orchestrionCfg.Metadata.Version, Origin: telemetry.OriginCode})
	}
	telemetryConfigs = append(telemetryConfigs, additionalConfigs...)
	telemetry.RegisterAppConfigs(telemetryConfigs...)
	cfg := telemetry.ClientConfig{
		HTTPClient: c.httpClient,
	}
	// Only omit the agent URL when the agent was reachable but explicitly does not
	// expose the telemetry proxy endpoint (e.g. the Datadog Lambda extension).
	// When the agent was unreachable at startup, we still set the URL so that
	// telemetry is attempted rather than silently dropped.
	// When the spans are emitted on stdout it means there is no agent at all in the env.
	if (!c.agent.reachable || c.agent.hasTelemetryProxy) && !c.internalConfig.LogToStdout() {
		cfg.AgentURL = c.agentURL.String()
	}
	if c.internalConfig.LogToStdout() || c.ciVisibilityAgentless {
		cfg.APIKey = env.Get("DD_API_KEY")
	}
	client, err := telemetry.NewClient(c.serviceName, c.internalConfig.Env(), c.internalConfig.Version(), cfg)
	if err != nil {
		log.Debug("tracer: failed to create telemetry client: %s", err.Error())
		return nil
	}

	if c.orchestrionCfg.Enabled {
		// If orchestrion is enabled, report it to the back-end via a telemetry metric on every flush.
		handle := client.Gauge(telemetry.NamespaceTracers, "orchestrion.enabled", []string{"version:" + c.orchestrionCfg.Metadata.Version})
		client.AddFlushTicker(func(_ telemetry.Client) { handle.Submit(1) })
	}

	telemetry.StartApp(client)
	return client
}
