import os
import sys
import sysconfig
import time
from typing import Any  # noqa:F401
from typing import Dict  # noqa:F401
from typing import Optional  # noqa:F401

import httpretty
import mock
import pytest

from ddtrace import config
from ddtrace.internal.compat import PYTHON_VERSION_INFO
import ddtrace.internal.telemetry
from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT
from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL
from ddtrace.internal.telemetry.data import get_application
from ddtrace.internal.telemetry.data import get_host_info
from ddtrace.internal.telemetry.writer import TelemetryWriter
from ddtrace.internal.telemetry.writer import get_runtime_id
from ddtrace.internal.utils.version import _pep440_to_semver
from ddtrace.settings._agent import get_agent_hostname
from ddtrace.settings._telemetry import config as telemetry_config
from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME
from tests.utils import call_program
from tests.utils import override_global_config


@pytest.mark.parametrize(
    "env_var,value,expected_value",
    [
        ("DD_APPSEC_SCA_ENABLED", "true", True),
        ("DD_APPSEC_SCA_ENABLED", "True", True),
        ("DD_APPSEC_SCA_ENABLED", "1", True),
        ("DD_APPSEC_SCA_ENABLED", "false", False),
        ("DD_APPSEC_SCA_ENABLED", "False", False),
        ("DD_APPSEC_SCA_ENABLED", "0", False),
    ],
)
def test_app_started_event_configuration_override_asm(
    test_agent_session, run_python_code_in_subprocess, env_var, value, expected_value
):
    """asserts that asm configuration value is changed and queues a valid telemetry request"""
    env = os.environ.copy()
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"
    env["DD_APPSEC_ENABLED"] = "true"
    env[env_var] = value
    _, stderr, status, _ = run_python_code_in_subprocess("import ddtrace.auto", env=env)
    assert status == 0, stderr

    configuration = test_agent_session.get_configurations(name=env_var, remove_seq_id=True, effective=True)
    assert len(configuration) == 1, configuration
    assert configuration[0] == {"name": env_var, "origin": "env_var", "value": expected_value}


def test_app_started_event(telemetry_writer, test_agent_session, mock_time):
    """asserts that app_started() queues a valid telemetry request which is then sent by periodic()"""
    with override_global_config(dict(_telemetry_dependency_collection=False)):
        # App started should be queued by the first periodic call
        telemetry_writer.periodic(force_flush=True)
        requests = test_agent_session.get_requests()
        assert len(requests) == 1
        assert requests[0]["headers"]["DD-Telemetry-Request-Type"] == "message-batch"
        app_started_events = test_agent_session.get_events("app-started")
        assert len(app_started_events) == 1
        validate_request_body(app_started_events[0], None, "app-started")
        assert len(app_started_events[0]["payload"]) == 2
        assert app_started_events[0]["payload"].get("configuration")
        assert app_started_events[0]["payload"].get("products")


def test_app_started_event_configuration_override(test_agent_session, run_python_code_in_subprocess, tmpdir):
    """
    asserts that default configuration value
    is changed and queues a valid telemetry request
    which is then sent by periodic()
    """
    code = """
# most configurations are reported when ddtrace.auto is imported
import ddtrace.auto
# report configurations not used by ddtrace.auto
import ddtrace.settings.symbol_db
import ddtrace.settings.dynamic_instrumentation
import ddtrace.settings.exception_replay
import opentelemetry
    """

    env = os.environ.copy()
    # Change configuration default values
    env["DD_EXCEPTION_REPLAY_ENABLED"] = "True"
    env["DD_INSTRUMENTATION_TELEMETRY_ENABLED"] = "True"
    env["DD_TRACE_STARTUP_LOGS"] = "True"
    env["DD_LOGS_INJECTION"] = "True"
    env["DD_DATA_STREAMS_ENABLED"] = "true"
    env["DD_APPSEC_ENABLED"] = "False"
    env["DD_RUNTIME_METRICS_ENABLED"] = "True"
    env["DD_SERVICE_MAPPING"] = "default_dd_service:remapped_dd_service"
    env["DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED"] = "True"
    env["DD_TRACE_CLIENT_IP_ENABLED"] = "True"
    env["DD_TRACE_COMPUTE_STATS"] = "True"
    env["DD_TRACE_DEBUG"] = "True"
    env["DD_TRACE_ENABLED"] = "False"
    env["DD_TRACE_HEALTH_METRICS_ENABLED"] = "True"
    env["DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP"] = ".*"
    env["DD_TRACE_OTEL_ENABLED"] = "True"
    env["DD_TRACE_PROPAGATION_STYLE_EXTRACT"] = "tracecontext"
    env["DD_TRACE_PROPAGATION_STYLE_INJECT"] = "tracecontext"
    env["DD_REMOTE_CONFIGURATION_ENABLED"] = "True"
    env["DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS"] = "1"
    env["DD_TRACE_RATE_LIMIT"] = "50"
    env["DD_TRACE_SAMPLING_RULES"] = '[{"sample_rate":1.0,"service":"xyz","name":"abc"}]'
    env["DD_PROFILING_ENABLED"] = "True"
    env["DD_PROFILING_STACK_ENABLED"] = "False"
    env["DD_PROFILING_MEMORY_ENABLED"] = "False"
    env["DD_PROFILING_HEAP_ENABLED"] = "False"
    env["DD_PROFILING_LOCK_ENABLED"] = "False"
    env["DD_PROFILING_CAPTURE_PCT"] = "5.0"
    env["DD_PROFILING_UPLOAD_INTERVAL"] = "10.0"
    env["DD_PROFILING_MAX_FRAMES"] = "512"
    env["DD_TRACE_SPAN_ATTRIBUTE_SCHEMA"] = "v1"
    env["DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED"] = "True"
    env["DD_TRACE_PEER_SERVICE_MAPPING"] = "default_service:remapped_service"
    env["DD_TRACE_API_VERSION"] = "v0.5"
    env["DD_TRACE_WRITER_BUFFER_SIZE_BYTES"] = "1000"
    env["DD_TRACE_WRITER_MAX_PAYLOAD_SIZE_BYTES"] = "9999"
    env["DD_TRACE_WRITER_INTERVAL_SECONDS"] = "30"
    env["DD_TRACE_WRITER_REUSE_CONNECTIONS"] = "True"
    env["DD_TAGS"] = "team:apm,component:web"
    env["DD_INSTRUMENTATION_CONFIG_ID"] = "abcedf123"
    env["DD_LOGS_OTEL_ENABLED"] = "True"
    env["DD_METRICS_OTEL_ENABLED"] = "True"
    env["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4317"

    file = tmpdir.join("moon_ears.json")
    file.write('[{"service":"xy?","name":"a*c"}]')
    env["DD_SPAN_SAMPLING_RULES"] = '[{"service":"xyz", "sample_rate":0.23}]'
    env["DD_SPAN_SAMPLING_RULES_FILE"] = str(file)
    env["DD_TRACE_PARTIAL_FLUSH_ENABLED"] = "false"
    env["DD_TRACE_PARTIAL_FLUSH_MIN_SPANS"] = "3"
    env["DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT"] = "restart"
    env["DD_SITE"] = "datadoghq.com"
    env["DD_APPSEC_RASP_ENABLED"] = "False"
    env["DD_API_SECURITY_ENABLED"] = "False"
    env["DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED"] = "False"
    env["DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE"] = "disabled"
    env["DD_INJECT_FORCE"] = "true"
    env["DD_INJECTION_ENABLED"] = "tracer"

    # Ensures app-started event is queued immediately after ddtrace is imported
    # instead of waiting for 10 seconds.
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    _, stderr, status, _ = run_python_code_in_subprocess(code, env=env)
    assert status == 0, stderr

    # DD_TRACE_AGENT_URL in gitlab is different from CI, to keep things simple we will
    # skip validating this config
    configurations = test_agent_session.get_configurations(
        ignores=["DD_TRACE_AGENT_URL", "DD_AGENT_PORT", "DD_TRACE_AGENT_PORT"], remove_seq_id=True, effective=True
    )
    assert configurations
    configurations.sort(key=lambda x: x["name"])

    expected = [
        {"name": "DD_AGENT_HOST", "origin": "default", "value": None},
        {"name": "DD_API_KEY", "origin": "default", "value": None},
        {"name": "DD_API_SECURITY_DOWNSTREAM_REQUEST_BODY_ANALYSIS_SAMPLE_RATE", "origin": "default", "value": 0.5},
        {"name": "DD_API_SECURITY_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED", "origin": "default", "value": True},
        {"name": "DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT", "origin": "default", "value": 300},
        {"name": "DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS", "origin": "default", "value": 1},
        {"name": "DD_API_SECURITY_PARSE_RESPONSE_BODY", "origin": "default", "value": True},
        {"name": "DD_API_SECURITY_SAMPLE_DELAY", "origin": "default", "value": 30.0},
        {"name": "DD_APM_TRACING_ENABLED", "origin": "default", "value": True},
        {"name": "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE", "origin": "env_var", "value": "disabled"},
        {"name": "DD_APPSEC_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_APPSEC_MAX_STACK_TRACES", "origin": "default", "value": 2},
        {"name": "DD_APPSEC_MAX_STACK_TRACE_DEPTH", "origin": "default", "value": 32},
        {"name": "DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT", "origin": "default", "value": 75.0},
        {
            "name": "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP",
            "origin": "default",
            "value": "(?i)pass|pw(?:or)?d|secret|(?:api|private|public|access)[_-]?key|token|consumer"
            "[_-]?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization|jsessionid|phpsessid|asp\\"
            ".net[_-]sessionid|sid|jwt",
        },
        {
            "name": "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP",
            "origin": "default",
            "value": r"(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|"
            r"secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)"
            r"key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?"
            r"|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)"
            r'(?:\s*=([^;&]+)|"\s*:\s*("[^"]+"|\d+))|bearer\s+([a-z0-9\._\-]+)|token\s*:\s*([a-z0-9]{13})|gh[opsu]_([0-9a-zA-Z]{36})'
            r"|ey[I-L][\w=-]+\.(ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?)|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}([^\-]+)[\-]"
            r"{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*([a-z0-9\/\.+]{100,})",
        },
        {"name": "DD_APPSEC_RASP_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_APPSEC_RULES", "origin": "default", "value": None},
        {
            "name": "DD_APPSEC_SCA_ENABLED",
            "origin": "default",
            "value": None,
        },
        {"name": "DD_APPSEC_STACK_TRACE_ENABLED", "origin": "default", "value": True},
        {"name": "DD_APPSEC_WAF_TIMEOUT", "origin": "default", "value": 5.0},
        {"name": "DD_CIVISIBILITY_AGENTLESS_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_CIVISIBILITY_AGENTLESS_URL", "origin": "default", "value": ""},
        {"name": "DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED", "origin": "default", "value": True},
        {"name": "DD_CIVISIBILITY_ITR_ENABLED", "origin": "default", "value": True},
        {"name": "DD_CIVISIBILITY_LOG_LEVEL", "origin": "default", "value": "info"},
        {"name": "DD_CODE_ORIGIN_FOR_SPANS_ENABLED", "origin": "default", "value": False},
        {"name": "DD_CRASHTRACKING_CREATE_ALT_STACK", "origin": "default", "value": True},
        {"name": "DD_CRASHTRACKING_DEBUG_URL", "origin": "default", "value": None},
        {"name": "DD_CRASHTRACKING_ENABLED", "origin": "default", "value": True},
        {"name": "DD_CRASHTRACKING_STACKTRACE_RESOLVER", "origin": "default", "value": "full"},
        {"name": "DD_CRASHTRACKING_STDERR_FILENAME", "origin": "default", "value": None},
        {"name": "DD_CRASHTRACKING_STDOUT_FILENAME", "origin": "default", "value": None},
        {"name": "DD_CRASHTRACKING_TAGS", "origin": "default", "value": ""},
        {"name": "DD_CRASHTRACKING_USE_ALT_STACK", "origin": "default", "value": True},
        {"name": "DD_CRASHTRACKING_WAIT_FOR_RECEIVER", "origin": "default", "value": True},
        {"name": "DD_DATA_STREAMS_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_DJANGO_INCLUDE_USER_EMAIL", "origin": "default", "value": False},
        {"name": "DD_DJANGO_INCLUDE_USER_LOGIN", "origin": "default", "value": True},
        {"name": "DD_DJANGO_INCLUDE_USER_NAME", "origin": "default", "value": True},
        {"name": "DD_DJANGO_INCLUDE_USER_REALNAME", "origin": "default", "value": False},
        {"name": "DD_DOGSTATSD_HOST", "origin": "default", "value": None},
        {"name": "DD_DOGSTATSD_PORT", "origin": "default", "value": None},
        {"name": "DD_DOGSTATSD_URL", "origin": "default", "value": None},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_DIAGNOSTICS_INTERVAL", "origin": "default", "value": 3600},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_ENABLED", "origin": "default", "value": False},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_MAX_PAYLOAD_SIZE", "origin": "default", "value": 1048576},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_METRICS_ENABLED", "origin": "default", "value": True},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE", "origin": "default", "value": None},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS", "origin": "default", "value": "set()"},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES", "origin": "default", "value": "set()"},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS", "origin": "default", "value": "set()"},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS", "origin": "default", "value": 1.0},
        {"name": "DD_DYNAMIC_INSTRUMENTATION_UPLOAD_TIMEOUT", "origin": "default", "value": 30},
        {"name": "DD_ENV", "origin": "default", "value": None},
        {"name": "DD_ERROR_TRACKING_HANDLED_ERRORS", "origin": "default", "value": ""},
        {"name": "DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE", "origin": "default", "value": ""},
        {"name": "DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES", "origin": "default", "value": 8},
        {"name": "DD_EXCEPTION_REPLAY_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_FASTAPI_ASYNC_BODY_TIMEOUT_SECONDS", "origin": "default", "value": 0.1},
        {"name": "DD_IAST_DEDUPLICATION_ENABLED", "origin": "default", "value": True},
        {"name": "DD_IAST_ENABLED", "origin": "default", "value": False},
        {"name": "DD_IAST_MAX_CONCURRENT_REQUESTS", "origin": "default", "value": 2},
        {"name": "DD_IAST_REDACTION_ENABLED", "origin": "default", "value": True},
        {
            "name": "DD_IAST_REDACTION_NAME_PATTERN",
            "origin": "default",
            "value": "(?i)^.*(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?"
            "|secret_?)key(?:_?id)?|password|token|username|user_id|last.name|consumer_?(?:id|key|secret)|sign("
            "?:ed|ature)?|auth(?:entication|orization)?)",
        },
        {
            "name": "DD_IAST_REDACTION_VALUE_NUMERAL",
            "origin": "default",
            "value": "^[+-]?((0b[01]+)|(0x[0-9A-Fa-f]+)|(\\d+\\.?\\d*(?:[Ee][+-]?\\d+)?|\\.\\d+(?:[Ee][+-]?"
            "\\d+)?)|(X\\'[0-9A-Fa-f]+\\')|(B\\'[01]+\\'))$",
        },
        {
            "name": "DD_IAST_REDACTION_VALUE_PATTERN",
            "origin": "default",
            "value": "(?i)bearer\\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|password|gh[opsu]_[0-9a-zA-Z]{36}|ey"
            "[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}"
            "[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}",
        },
        {"name": "DD_IAST_REQUEST_SAMPLING", "origin": "default", "value": 30.0},
        {"name": "DD_IAST_SECURITY_CONTROLS_CONFIGURATION", "origin": "default", "value": ""},
        {"name": "DD_IAST_STACK_TRACE_ENABLED", "origin": "default", "value": True},
        {"name": "DD_IAST_TELEMETRY_VERBOSITY", "origin": "default", "value": "INFORMATION"},
        {"name": "DD_IAST_VULNERABILITIES_PER_REQUEST", "origin": "default", "value": 2},
        {"name": "DD_INJECTION_ENABLED", "origin": "env_var", "value": "tracer"},
        {"name": "DD_INJECT_FORCE", "origin": "env_var", "value": True},
        {"name": "DD_INSTRUMENTATION_TELEMETRY_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_LIVE_DEBUGGING_ENABLED", "origin": "default", "value": False},
        {"name": "DD_LLMOBS_AGENTLESS_ENABLED", "origin": "default", "value": None},
        {"name": "DD_LLMOBS_ENABLED", "origin": "default", "value": False},
        {"name": "DD_LLMOBS_INSTRUMENTED_PROXY_URLS", "origin": "default", "value": None},
        {"name": "DD_LLMOBS_ML_APP", "origin": "default", "value": None},
        {"name": "DD_LLMOBS_SAMPLE_RATE", "origin": "default", "value": 1.0},
        {"name": "DD_LOGS_INJECTION", "origin": "env_var", "value": True},
        {"name": "DD_LOGS_OTEL_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_METRICS_OTEL_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_PROFILING_AGENTLESS", "origin": "default", "value": False},
        {"name": "DD_PROFILING_API_TIMEOUT_MS", "origin": "default", "value": 10000},
        {"name": "DD_PROFILING_CAPTURE_PCT", "origin": "env_var", "value": 5.0},
        {"name": "DD_PROFILING_ENABLED", "origin": "env_var", "value": PYTHON_VERSION_INFO < (3, 14)},
        {"name": "DD_PROFILING_ENABLE_ASSERTS", "origin": "default", "value": False},
        {"name": "DD_PROFILING_ENABLE_CODE_PROVENANCE", "origin": "default", "value": True},
        {"name": "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", "origin": "default", "value": True},
        {"name": "DD_PROFILING_HEAP_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_PROFILING_HEAP_SAMPLE_SIZE", "origin": "default", "value": None},
        {"name": "DD_PROFILING_IGNORE_PROFILER", "origin": "default", "value": False},
        {"name": "DD_PROFILING_LOCK_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_PROFILING_LOCK_NAME_INSPECT_DIR", "origin": "default", "value": True},
        {"name": "DD_PROFILING_MAX_FRAMES", "origin": "env_var", "value": 512},
        {"name": "DD_PROFILING_MAX_TIME_USAGE_PCT", "origin": "default", "value": 1.0},
        {"name": "DD_PROFILING_MEMORY_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_PROFILING_MEMORY_EVENTS_BUFFER", "origin": "default", "value": 16},
        {"name": "DD_PROFILING_OUTPUT_PPROF", "origin": "default", "value": None},
        {"name": "DD_PROFILING_PYTORCH_ENABLED", "origin": "default", "value": False},
        {"name": "DD_PROFILING_PYTORCH_EVENTS_LIMIT", "origin": "default", "value": 1000000},
        {"name": "DD_PROFILING_SAMPLE_POOL_CAPACITY", "origin": "default", "value": 4},
        {"name": "DD_PROFILING_STACK_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_PROFILING_STACK_V2_ENABLED", "origin": "default", "value": PYTHON_VERSION_INFO < (3, 14)},
        {"name": "DD_PROFILING_TAGS", "origin": "default", "value": ""},
        {"name": "DD_PROFILING_TIMELINE_ENABLED", "origin": "default", "value": True},
        {"name": "DD_PROFILING_UPLOAD_INTERVAL", "origin": "env_var", "value": 10.0},
        {"name": "DD_REMOTE_CONFIGURATION_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "origin": "env_var", "value": 1.0},
        {"name": "DD_RUNTIME_METRICS_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_SERVICE", "origin": "default", "value": DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME},
        {"name": "DD_SERVICE_MAPPING", "origin": "env_var", "value": "default_dd_service:remapped_dd_service"},
        {"name": "DD_SITE", "origin": "env_var", "value": "datadoghq.com"},
        {"name": "DD_SPAN_SAMPLING_RULES", "origin": "env_var", "value": '[{"service":"xyz", "sample_rate":0.23}]'},
        {
            "name": "DD_SPAN_SAMPLING_RULES_FILE",
            "origin": "env_var",
            "value": str(file),
        },
        {"name": "DD_SYMBOL_DATABASE_INCLUDES", "origin": "default", "value": "set()"},
        {"name": "DD_SYMBOL_DATABASE_UPLOAD_ENABLED", "origin": "default", "value": True},
        {"name": "DD_TAGS", "origin": "env_var", "value": "team:apm,component:web"},
        {"name": "DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED", "origin": "default", "value": True},
        {"name": "DD_TELEMETRY_HEARTBEAT_INTERVAL", "origin": "default", "value": 60},
        {"name": "DD_TESTING_RAISE", "origin": "env_var", "value": True},
        {"name": "DD_TEST_SESSION_NAME", "origin": "default", "value": None},
        {"name": "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "origin": "default", "value": False},
        {"name": "DD_TRACE_AGENT_HOSTNAME", "origin": "default", "value": None},
        {"name": "DD_TRACE_AGENT_TIMEOUT_SECONDS", "origin": "default", "value": 2.0},
        {"name": "DD_TRACE_API_VERSION", "origin": "env_var", "value": "v0.5"},
        {"name": "DD_TRACE_BAGGAGE_TAG_KEYS", "origin": "default", "value": "user.id,account.id,session.id"},
        {"name": "DD_TRACE_CLIENT_IP_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_CLIENT_IP_HEADER", "origin": "default", "value": None},
        {"name": "DD_TRACE_COMPUTE_STATS", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_DEBUG", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED", "origin": "default", "value": "set()"},
        {"name": "DD_TRACE_EXPERIMENTAL_LONG_RUNNING_FLUSH_INTERVAL", "origin": "default", "value": 120.0},
        {"name": "DD_TRACE_EXPERIMENTAL_LONG_RUNNING_INITIAL_FLUSH_INTERVAL", "origin": "default", "value": 10.0},
        {"name": "DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED", "origin": "default", "value": False},
        {"name": "DD_TRACE_HEADER_TAGS", "origin": "default", "value": ""},
        {"name": "DD_TRACE_HEALTH_METRICS_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "origin": "default", "value": "true"},
        {"name": "DD_TRACE_HTTP_SERVER_ERROR_STATUSES", "origin": "default", "value": "500-599"},
        {"name": "DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED", "origin": "default", "value": False},
        {"name": "DD_TRACE_LOG_FILE", "origin": "default", "value": None},
        {"name": "DD_TRACE_LOG_FILE_LEVEL", "origin": "default", "value": "DEBUG"},
        {"name": "DD_TRACE_LOG_FILE_SIZE_BYTES", "origin": "default", "value": 15728640},
        {"name": "DD_TRACE_LOG_STREAM_HANDLER", "origin": "default", "value": True},
        {"name": "DD_TRACE_METHODS", "origin": "default", "value": None},
        {"name": "DD_TRACE_NATIVE_SPAN_EVENTS", "origin": "default", "value": False},
        {"name": "DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP", "origin": "env_var", "value": ".*"},
        {"name": "DD_TRACE_OTEL_ENABLED", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_PARTIAL_FLUSH_ENABLED", "origin": "env_var", "value": False},
        {"name": "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", "origin": "env_var", "value": 3},
        {
            "name": "DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED",
            "origin": "default",
            "value": False,
        },
        {
            "name": "DD_TRACE_PEER_SERVICE_MAPPING",
            "origin": "env_var",
            "value": "default_service:remapped_service",
        },
        {"name": "DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT", "origin": "env_var", "value": "restart"},
        {"name": "DD_TRACE_PROPAGATION_EXTRACT_FIRST", "origin": "default", "value": False},
        {"name": "DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED", "origin": "default", "value": False},
        {"name": "DD_TRACE_PROPAGATION_STYLE_EXTRACT", "origin": "env_var", "value": "tracecontext"},
        {"name": "DD_TRACE_PROPAGATION_STYLE_INJECT", "origin": "env_var", "value": "tracecontext"},
        {"name": "DD_TRACE_RATE_LIMIT", "origin": "env_var", "value": 50},
        {"name": "DD_TRACE_REPORT_HOSTNAME", "origin": "default", "value": False},
        {"name": "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT", "origin": "default", "value": False},
        {"name": "DD_TRACE_RESOURCE_RENAMING_ENABLED", "origin": "default", "value": False},
        {"name": "DD_TRACE_SAFE_INSTRUMENTATION_ENABLED", "origin": "default", "value": False},
        {
            "name": "DD_TRACE_SAMPLING_RULES",
            "origin": "env_var",
            "value": '[{"sample_rate":1.0,"service":"xyz","name":"abc"}]',
        },
        {"name": "DD_TRACE_SPAN_TRACEBACK_MAX_SIZE", "origin": "default", "value": 30},
        {"name": "DD_TRACE_STARTUP_LOGS", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_WRITER_BUFFER_SIZE_BYTES", "origin": "env_var", "value": 1000},
        {"name": "DD_TRACE_WRITER_INTERVAL_SECONDS", "origin": "env_var", "value": 30.0},
        {"name": "DD_TRACE_WRITER_MAX_PAYLOAD_SIZE_BYTES", "origin": "env_var", "value": 9999},
        {"name": "DD_TRACE_WRITER_REUSE_CONNECTIONS", "origin": "env_var", "value": True},
        {"name": "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH", "origin": "default", "value": 512},
        {"name": "DD_USER_MODEL_EMAIL_FIELD", "origin": "default", "value": ""},
        {"name": "DD_USER_MODEL_LOGIN_FIELD", "origin": "default", "value": ""},
        {"name": "DD_USER_MODEL_NAME_FIELD", "origin": "default", "value": ""},
        {"name": "DD_VERSION", "origin": "default", "value": None},
        {
            "name": "OTEL_EXPORTER_OTLP_ENDPOINT",
            "origin": "env_var",
            "value": "http://localhost:4317",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_HEADERS",
            "origin": "default",
            "value": "",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
            "origin": "default",
            "value": f"http://{get_agent_hostname()}:4317",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
            "origin": "default",
            "value": "",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL",
            "origin": "default",
            "value": "grpc",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT",
            "origin": "default",
            "value": 10000,
        },
        {
            "name": "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
            "origin": "default",
            "value": f"http://{get_agent_hostname()}:4317",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
            "origin": "default",
            "value": "",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL",
            "origin": "default",
            "value": "grpc",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE",
            "origin": "default",
            "value": "delta",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT",
            "origin": "default",
            "value": 10000,
        },
        {
            "name": "OTEL_EXPORTER_OTLP_PROTOCOL",
            "origin": "default",
            "value": "grpc",
        },
        {
            "name": "OTEL_EXPORTER_OTLP_TIMEOUT",
            "origin": "default",
            "value": 10000,
        },
        {
            "name": "OTEL_METRIC_EXPORT_INTERVAL",
            "origin": "default",
            "value": 10000,
        },
        {
            "name": "OTEL_METRIC_EXPORT_TIMEOUT",
            "origin": "default",
            "value": 7500,
        },
        {"name": "_DD_APPSEC_DEDUPLICATION_ENABLED", "origin": "default", "value": True},
        {"name": "_DD_IAST_LAZY_TAINT", "origin": "default", "value": False},
        {"name": "_DD_IAST_USE_ROOT_SPAN", "origin": "default", "value": False},
        {"name": "_DD_NATIVE_LOGGING_BACKEND", "origin": "default", "value": None},
        {"name": "_DD_TRACE_WRITER_LOG_ERROR_PAYLOADS", "origin": "default", "value": False},
        {"name": "_DD_TRACE_WRITER_NATIVE", "origin": "default", "value": True},
        {"name": "instrumentation_source", "origin": "code", "value": "manual"},
        {"name": "python_build_gnu_type", "origin": "unknown", "value": sysconfig.get_config_var("BUILD_GNU_TYPE")},
        {"name": "python_host_gnu_type", "origin": "unknown", "value": sysconfig.get_config_var("HOST_GNU_TYPE")},
        {"name": "python_soabi", "origin": "unknown", "value": sysconfig.get_config_var("SOABI")},
    ]
    assert configurations == expected, configurations


def test_update_dependencies_event(test_agent_session, ddtrace_run_python_code_in_subprocess):
    env = os.environ.copy()
    # app-started events are sent 10 seconds after ddtrace imported, this configuration overrides this
    # behavior to force the app-started event to be queued immediately
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    # Import httppretty after ddtrace is imported, this ensures that the module is sent in a dependencies event
    # Imports httpretty twice and ensures only one dependency entry is sent
    _, stderr, status, _ = ddtrace_run_python_code_in_subprocess("import xmltodict", env=env)
    assert status == 0, stderr
    deps = test_agent_session.get_dependencies("xmltodict")
    assert len(deps) == 1, deps


def test_endpoint_discovery_event(test_agent_session, ddtrace_run_python_code_in_subprocess):
    env = os.environ.copy()
    # app-started events are sent 10 seconds after ddtrace imported, this configuration overrides this
    # behavior to force the app-started event to be queued immediately
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    # Import httppretty after ddtrace is imported, this ensures that the module is sent in a dependencies event
    # Imports httpretty twice and ensures only one dependency entry is sent

    mini_django_app = """
from os import path as osp
def rel_path(*p): return osp.normpath(osp.join(rel_path.path, *p))
rel_path.path = osp.abspath(osp.dirname(__file__))
this = osp.splitext(osp.basename(__file__))[0]
from django.conf import settings
SETTINGS = dict(
    DATABASES = {},
    DEBUG=True,
    TEMPLATE_DEBUG=True,
    ROOT_URLCONF = this
)
SETTINGS['DATABASES']={
    'default':{
        'ENGINE':'django.db.backends.sqlite3',
        'NAME':rel_path('db')
    }
}

if __name__=='__main__':
    settings.configure(**SETTINGS)

if __name__ == '__main__':
    from django.core import management
    management.execute_from_command_line()

from django.urls import path
from django.http import HttpResponse
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET"])
def view_name(request):
    return HttpResponse('response text')
def mini_app(request):
    return HttpResponse('response text')
urlpatterns = [ path('mini_app/',mini_app), path('view_name/', view_name) ]
"""

    _, stderr, status, _ = ddtrace_run_python_code_in_subprocess(mini_django_app, env=env)
    assert status == 0, stderr
    deps = test_agent_session.get_dependencies("django")
    assert len(deps) == 1, deps

    events = test_agent_session.get_events("app-endpoints")
    assert len(events) == 1, events
    payload = events[0]["payload"]
    assert payload["is_first"] is True
    endpoints = payload["endpoints"]
    assert len(endpoints) == 2, endpoints
    assert any(
        e["path"] == "mini_app/" and e["method"] == "*" and e["operation_name"] == "django.request" for e in endpoints
    ), endpoints
    assert any(
        e["path"] == "view_name/"
        and e["method"] == "GET"
        and e["resource_name"] == "GET view_name/"
        and e["operation_name"] == "django.request"
        for e in endpoints
    ), endpoints


def test_instrumentation_source_config(
    test_agent_session, ddtrace_run_python_code_in_subprocess, run_python_code_in_subprocess
):
    env = os.environ.copy()
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    _, stderr, status, _ = call_program("ddtrace-run", sys.executable, "-c", "", env=env)
    assert status == 0, stderr
    configs = test_agent_session.get_configurations("instrumentation_source")
    assert configs and configs[-1]["value"] == "cmd_line"
    test_agent_session.clear()

    _, stderr, status, _ = call_program(sys.executable, "-c", "import ddtrace.auto", env=env)
    assert status == 0, stderr
    configs = test_agent_session.get_configurations("instrumentation_source")
    assert configs and configs[-1]["value"] == "manual"
    test_agent_session.clear()

    _, stderr, status, _ = call_program(sys.executable, "-c", "import ddtrace", env=env)
    assert status == 0, stderr
    configs = test_agent_session.get_configurations("instrumentation_source")
    assert not configs, "instrumentation_source should not be set when ddtrace instrumentation is not used"


def test_update_dependencies_event_when_disabled(test_agent_session, ddtrace_run_python_code_in_subprocess):
    env = os.environ.copy()
    # app-started events are sent 10 seconds after ddtrace imported, this configuration overrides this
    # behavior to force the app-started event to be queued immediately
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"
    env["DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED"] = "false"

    # Import httppretty after ddtrace is imported, this ensures that the module is sent in a dependencies event
    # Imports httpretty twice and ensures only one dependency entry is sent
    _, stderr, status, _ = ddtrace_run_python_code_in_subprocess("import xmltodict", env=env)
    events = test_agent_session.get_events("app-dependencies-loaded")
    assert len(events) == 0, events


def test_update_dependencies_event_not_stdlib(test_agent_session, ddtrace_run_python_code_in_subprocess):
    env = os.environ.copy()
    # app-started events are sent 10 seconds after ddtrace imported, this configuration overrides this
    # behavior to force the app-started event to be queued immediately
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    # Import httppretty after ddtrace is imported, this ensures that the module is sent in a dependencies event
    # Imports httpretty twice and ensures only one dependency entry is sent
    _, stderr, status, _ = ddtrace_run_python_code_in_subprocess(
        """
import sys
import httpretty
del sys.modules["httpretty"]
import httpretty
""",
        env=env,
    )
    assert status == 0, stderr
    deps = test_agent_session.get_dependencies("httpretty")
    assert len(deps) == 1, deps


def test_app_closing_event(telemetry_writer, test_agent_session, mock_time):
    """asserts that app_shutdown() queues and sends an app-closing telemetry request"""
    # Telemetry writer must start before app-closing event is queued
    telemetry_writer.started = True
    # send app closed event
    telemetry_writer.app_shutdown()
    # ensure a valid request body was sent
    events = test_agent_session.get_events("app-closing")
    assert len(events) == 1
    validate_request_body(events[0], {}, "app-closing", 1)


def test_add_integration(telemetry_writer, test_agent_session, mock_time):
    """asserts that add_integration() queues a valid telemetry request"""
    with override_global_config(dict(_telemetry_dependency_collection=False)):
        # queue integrations
        telemetry_writer.add_integration("integration-t", True, True, "")
        telemetry_writer.add_integration("integration-f", False, False, "terrible failure")
        # send integrations to the agent
        telemetry_writer.periodic(force_flush=True)

        events = test_agent_session.get_events("app-integrations-change")
        # assert integration change telemetry request was sent
        assert len(events) == 1
        # assert that the request had a valid request body
        events[0]["payload"]["integrations"].sort(key=lambda x: x["name"])
        expected_payload = {
            "integrations": [
                {
                    "name": "integration-f",
                    "version": "",
                    "enabled": False,
                    "auto_enabled": False,
                    "compatible": False,
                    "error": "terrible failure",
                },
                {
                    "name": "integration-t",
                    "version": "",
                    "enabled": True,
                    "auto_enabled": True,
                    "compatible": True,
                    "error": "",
                },
            ]
        }
        validate_request_body(events[0], expected_payload, "app-integrations-change")


def test_app_client_configuration_changed_event(telemetry_writer, test_agent_session, mock_time):
    # force periodic call to flush the first app_started call
    telemetry_writer.periodic(force_flush=True)
    """asserts that queuing a configuration sends a valid telemetry request"""
    with override_global_config(dict()):
        telemetry_writer.add_configuration("appsec_enabled", True, "env_var")
        telemetry_writer.add_configuration("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "datadog", "default")
        telemetry_writer.add_configuration("appsec_enabled", False, "code")

        telemetry_writer.periodic(force_flush=True)

        events = test_agent_session.get_events("app-client-configuration-change")
        received_configurations = [c for event in events for c in event["payload"]["configuration"]]
        received_configurations.sort(key=lambda c: c["seq_id"])
        assert (
            received_configurations[0]["seq_id"]
            < received_configurations[1]["seq_id"]
            < received_configurations[2]["seq_id"]
        )
        # assert that all configuration values are sent to the agent in the order they were added (by seq_id)
        assert received_configurations[0]["name"] == "appsec_enabled"
        assert received_configurations[0]["origin"] == "env_var"
        assert received_configurations[0]["value"] is True
        assert received_configurations[1]["name"] == "DD_TRACE_PROPAGATION_STYLE_EXTRACT"
        assert received_configurations[1]["origin"] == "default"
        assert received_configurations[1]["value"] == "datadog"
        assert received_configurations[2]["name"] == "appsec_enabled"
        assert received_configurations[2]["origin"] == "code"
        assert received_configurations[2]["value"] is False


def test_add_integration_disabled_writer(telemetry_writer, test_agent_session):
    """asserts that add_integration() does not queue an integration when telemetry is disabled"""
    telemetry_writer.disable()

    telemetry_writer.add_integration("integration-name", True, False, "")
    telemetry_writer.periodic(force_flush=True)
    assert len(test_agent_session.get_events("app-integrations-change")) == 0


@pytest.mark.parametrize("mock_status", [300, 400, 401, 403, 500])
def test_send_failing_request(mock_status, telemetry_writer):
    """asserts that a warning is logged when an unsuccessful response is returned by the http client"""

    with override_global_config(dict(_telemetry_dependency_collection=False)):
        # force periodic call to flush the first app_started call
        telemetry_writer.periodic(force_flush=True)
        with httpretty.enabled():
            httpretty.register_uri(httpretty.POST, telemetry_writer._client.url, status=mock_status)
            with mock.patch("ddtrace.internal.telemetry.writer.log") as log:
                # sends failing app-heartbeat event
                telemetry_writer.periodic(force_flush=True)
                # asserts unsuccessful status code was logged
                log.debug.assert_called_with(
                    "Failed to send Instrumentation Telemetry to %s. Response: %s",
                    telemetry_writer._client.url,
                    mock_status,
                )


def test_app_heartbeat_event_periodic(mock_time, telemetry_writer, test_agent_session):
    # type: (mock.Mock, Any, Any) -> None
    """asserts that we queue/send app-heartbeat when periodc() is called"""
    # Ensure telemetry writer is initialized to send periodic events
    telemetry_writer._is_periodic = True
    telemetry_writer.started = True
    # Assert default telemetry interval is 10 seconds and the expected periodic threshold and counts are set
    assert telemetry_writer.interval == 10
    assert telemetry_writer._periodic_threshold == 5
    assert telemetry_writer._periodic_count == 0

    # Assert next flush contains app-heartbeat event
    for _ in range(telemetry_writer._periodic_threshold):
        telemetry_writer.periodic()
        assert test_agent_session.get_events(mock.ANY, filter_heartbeats=False) == []

    telemetry_writer.periodic()
    heartbeat_events = test_agent_session.get_events("app-heartbeat", filter_heartbeats=False)
    assert len(heartbeat_events) == 1


def test_app_heartbeat_event(mock_time, telemetry_writer, test_agent_session):
    # type: (mock.Mock, Any, Any) -> None
    """asserts that we queue/send app-heartbeat event every 60 seconds when app_heartbeat_event() is called"""
    # Assert a maximum of one heartbeat is queued per flush
    telemetry_writer.periodic(force_flush=True)
    events = test_agent_session.get_events(mock.ANY, filter_heartbeats=False)
    assert len(events) > 0


def test_app_product_change_event(mock_time, telemetry_writer, test_agent_session):
    # type: (mock.Mock, Any, Any) -> None
    """asserts that enabling or disabling an APM Product triggers a valid telemetry request"""

    # Assert that the default product status is disabled
    assert any(telemetry_writer._product_enablement.values()) is False
    # Assert that the product status is first reported in app-started event
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.LLMOBS, True)
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.DYNAMIC_INSTRUMENTATION, True)
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.PROFILER, True)
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True)
    assert all(telemetry_writer._product_enablement.values())

    telemetry_writer.periodic(force_flush=True)

    # Assert that there's only an app_started event (since product activation happened before the app started)
    events = test_agent_session.get_events("app-product-change")
    telemetry_writer.periodic(force_flush=True)
    assert not len(events)
    # Assert that unchanged status doesn't generate the event
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.PROFILER, True)
    telemetry_writer.periodic(force_flush=True)
    events = test_agent_session.get_events("app-product-change")
    assert not len(events)
    # Assert that product change event is sent when product status changes
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, False)
    telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.DYNAMIC_INSTRUMENTATION, False)
    telemetry_writer.periodic(force_flush=True)
    events = test_agent_session.get_events("app-product-change")
    assert len(events) == 1
    assert events[0]["request_type"] == "app-product-change"
    products = events[0]["payload"]["products"]
    version = _pep440_to_semver()
    assert products == {
        TELEMETRY_APM_PRODUCT.APPSEC.value: {"enabled": False, "version": version},
        TELEMETRY_APM_PRODUCT.DYNAMIC_INSTRUMENTATION.value: {"enabled": False, "version": version},
    }


def validate_request_body(received_body, payload, payload_type, seq_id=None):
    # type: (Dict, Dict, str, Optional[int]) -> Dict
    """used to test the body of requests received by the testagent"""
    assert len(received_body) == 9
    assert received_body["tracer_time"] == time.time()
    assert received_body["runtime_id"] == get_runtime_id()
    assert received_body["api_version"] == "v2"
    assert received_body["debug"] is False
    if seq_id is not None:
        assert received_body["seq_id"] == seq_id
    assert received_body["application"] == get_application(config.service, config.version, config.env)
    assert received_body["host"] == get_host_info()
    if payload is not None:
        assert received_body["payload"] == payload
    assert received_body["request_type"] == payload_type


def test_telemetry_writer_agent_setup():
    with override_global_config(
        {"_dd_site": "datad0g.com", "_dd_api_key": "foobarkey", "_ci_visibility_agentless_enabled": False}
    ):
        new_telemetry_writer = ddtrace.internal.telemetry.TelemetryWriter(agentless=False)
        assert new_telemetry_writer._enabled
        assert new_telemetry_writer._client._endpoint == "telemetry/proxy/api/v2/apmtelemetry"
        assert "http://" in new_telemetry_writer._client._telemetry_url
        assert ":9126" in new_telemetry_writer._client._telemetry_url
        assert "dd-api-key" not in new_telemetry_writer._client._headers


@pytest.mark.parametrize(
    "env_agentless,arg_agentless,expected_endpoint",
    [
        (True, True, "api/v2/apmtelemetry"),
        (True, False, "telemetry/proxy/api/v2/apmtelemetry"),
        (False, True, "api/v2/apmtelemetry"),
        (False, False, "telemetry/proxy/api/v2/apmtelemetry"),
    ],
)
def test_telemetry_writer_agent_setup_agentless_arg_overrides_env(env_agentless, arg_agentless, expected_endpoint):
    with override_global_config(
        {"_dd_site": "datad0g.com", "_dd_api_key": "foobarkey", "_ci_visibility_agentless_enabled": env_agentless}
    ):
        new_telemetry_writer = ddtrace.internal.telemetry.TelemetryWriter(agentless=arg_agentless)
        # Note: other tests are checking whether values bet set properly, so we're only looking at agentlessness here
        assert new_telemetry_writer._client._endpoint == expected_endpoint


@pytest.mark.subprocess(
    env={"DD_SITE": "datad0g.com", "DD_API_KEY": "foobarkey", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "true"}
)
def test_telemetry_writer_agentless_setup():
    from ddtrace.internal.telemetry import telemetry_writer

    assert telemetry_writer._enabled
    assert telemetry_writer._client._endpoint == "api/v2/apmtelemetry"
    assert telemetry_writer._client._telemetry_url == "https://all-http-intake.logs.datad0g.com"
    assert telemetry_writer._client._headers["dd-api-key"] == "foobarkey"


@pytest.mark.subprocess(
    env={"DD_SITE": "datadoghq.eu", "DD_API_KEY": "foobarkey", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "true"}
)
def test_telemetry_writer_agentless_setup_eu():
    from ddtrace.internal.telemetry import telemetry_writer

    assert telemetry_writer._enabled
    assert telemetry_writer._client._endpoint == "api/v2/apmtelemetry"
    assert telemetry_writer._client._telemetry_url == "https://instrumentation-telemetry-intake.datadoghq.eu"
    assert telemetry_writer._client._headers["dd-api-key"] == "foobarkey"


@pytest.mark.subprocess(env={"DD_SITE": "datad0g.com", "DD_API_KEY": "", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "true"})
def test_telemetry_writer_agentless_disabled_without_api_key():
    from ddtrace.internal.telemetry import telemetry_writer

    assert not telemetry_writer._enabled
    assert telemetry_writer._client._endpoint == "api/v2/apmtelemetry"
    assert telemetry_writer._client._telemetry_url == "https://all-http-intake.logs.datad0g.com"
    assert "dd-api-key" not in telemetry_writer._client._headers


@pytest.mark.subprocess(env={"DD_SITE": "datad0g.com", "DD_API_KEY": "foobarkey"})
def test_telemetry_writer_is_using_agentless_by_default_if_api_key_is_available():
    from ddtrace.internal.telemetry import telemetry_writer

    assert telemetry_writer._enabled
    assert telemetry_writer._client._endpoint == "api/v2/apmtelemetry"
    assert telemetry_writer._client._telemetry_url == "https://all-http-intake.logs.datad0g.com"
    assert telemetry_writer._client._headers["dd-api-key"] == "foobarkey"


@pytest.mark.subprocess(env={"DD_API_KEY": "", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "false"})
def test_telemetry_writer_is_using_agent_by_default_if_api_key_is_not_available():
    from ddtrace.internal.telemetry import telemetry_writer

    assert telemetry_writer._enabled
    assert telemetry_writer._client._endpoint == "telemetry/proxy/api/v2/apmtelemetry"
    assert telemetry_writer._client._telemetry_url in ("http://localhost:9126", "http://testagent:9126")
    assert "dd-api-key" not in telemetry_writer._client._headers


def test_otel_config_telemetry(test_agent_session, run_python_code_in_subprocess, tmpdir):
    """
    asserts that telemetry data is submitted for OpenTelemetry configurations
    """

    env = os.environ.copy()
    env["DD_SERVICE"] = "dd_service"
    env["OTEL_SERVICE_NAME"] = "otel_service"
    env["OTEL_LOG_LEVEL"] = "DEBUG"
    env["OTEL_PROPAGATORS"] = "tracecontext"
    env["OTEL_TRACES_SAMPLER"] = "always_on"
    env["OTEL_TRACES_EXPORTER"] = "none"
    env["OTEL_LOGS_EXPORTER"] = "otlp"
    env["OTEL_METRICS_EXPORTER"] = "otlp"
    env["OTEL_RESOURCE_ATTRIBUTES"] = "team=apm,component=web"
    env["OTEL_SDK_DISABLED"] = "true"
    env["OTEL_UNSUPPORTED_CONFIG"] = "value"
    env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true"

    _, stderr, status, _ = run_python_code_in_subprocess("import ddtrace", env=env)
    assert status == 0, stderr

    configurations = {c["name"]: c for c in test_agent_session.get_configurations(remove_seq_id=True, effective=True)}

    assert configurations["DD_SERVICE"] == {"name": "DD_SERVICE", "origin": "env_var", "value": "dd_service"}
    assert configurations["DD_TRACE_DEBUG"] == {"name": "DD_TRACE_DEBUG", "origin": "otel_env_var", "value": "debug"}
    assert configurations["DD_TRACE_PROPAGATION_STYLE_INJECT"] == {
        "name": "DD_TRACE_PROPAGATION_STYLE_INJECT",
        "origin": "otel_env_var",
        "value": "tracecontext",
    }
    assert configurations["DD_TRACE_PROPAGATION_STYLE_EXTRACT"] == {
        "name": "DD_TRACE_PROPAGATION_STYLE_EXTRACT",
        "origin": "otel_env_var",
        "value": "tracecontext",
    }
    assert configurations["DD_TRACE_SAMPLING_RULES"] == {
        "name": "DD_TRACE_SAMPLING_RULES",
        "origin": "otel_env_var",
        "value": "always_on",
    }
    assert configurations["DD_TRACE_ENABLED"] == {
        "name": "DD_TRACE_ENABLED",
        "origin": "otel_env_var",
        "value": "none",
    }
    assert configurations["DD_TAGS"] == {
        "name": "DD_TAGS",
        "origin": "otel_env_var",
        "value": "team=apm,component=web",
    }
    assert configurations["DD_TRACE_OTEL_ENABLED"] == {
        "name": "DD_TRACE_OTEL_ENABLED",
        "origin": "otel_env_var",
        "value": "true",
    }

    env_hiding_metrics = test_agent_session.get_metrics("otel.env.hiding")
    tags = [m["tags"] for m in env_hiding_metrics]
    assert tags == [["config_opentelemetry:otel_service_name", "config_datadog:dd_service"]]

    env_unsupported_metrics = test_agent_session.get_metrics("otel.env.unsupported")
    tags = [m["tags"] for m in env_unsupported_metrics]
    assert tags == [["config_opentelemetry:otel_unsupported_config"]]

    env_invalid_metrics = test_agent_session.get_metrics("otel.env.invalid")
    tags = [m["tags"] for m in env_invalid_metrics]
    assert tags == [["config_opentelemetry:otel_logs_exporter"]]


def test_add_error_log(mock_time, telemetry_writer, test_agent_session):
    """Test add_integration_error_log functionality with real stack trace"""
    try:
        import json

        json.loads("{invalid: json,}")
    except Exception as e:
        telemetry_writer.add_error_log("Test error message", e)
        telemetry_writer.periodic(force_flush=True)

        log_events = test_agent_session.get_events("logs")
        assert len(log_events) == 1

        logs = log_events[0]["payload"]["logs"]
        assert len(logs) == 1

        log_entry = logs[0]
        assert log_entry["level"] == TELEMETRY_LOG_LEVEL.ERROR.value
        assert log_entry["message"] == "Test error message"
        assert log_entry["tags"] == "error_type:jsondecodeerror"

        stack_trace = log_entry["stack_trace"]
        expected_lines = [
            "Traceback (most recent call last):",
            "<REDACTED>",  # User code gets redacted
            '  File "json/__init__.py',
            "    return _default_decoder.decode(s)",
            '  File "json/decoder.py"',
            "    obj, end = self.raw_decode(s, idx=_w(s, 0).end())",
            '  File "json/decoder.py"',
            "    obj, end = self.scan_once(s, idx)",
            "json.decoder.JSONDecodeError: <REDACTED>",
        ]
        for expected_line in expected_lines:
            assert expected_line in stack_trace


def test_add_error_log_large_stack(mock_time, telemetry_writer, test_agent_session):
    """Test add_integration_error_log functionality with real stack trace"""
    try:

        def _(n):
            if n == 200:
                raise ValueError("Test exception for large stack trace")
            return _(n + 1)

        _(0)
    except Exception as e:
        telemetry_writer.add_error_log("Test error message", e)
        telemetry_writer.periodic(force_flush=True)

        log_events = test_agent_session.get_events("logs")
        assert len(log_events) == 1

        logs = log_events[0]["payload"]["logs"]
        assert len(logs) == 1

        log_entry = logs[0]
        assert log_entry["level"] == TELEMETRY_LOG_LEVEL.ERROR.value
        assert log_entry["message"] == "Test error message"
        assert log_entry["tags"] == "error_type:valueerror"

        stack_trace = log_entry["stack_trace"]
        expected_lines = """Traceback (most recent call last):
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
  <REDACTED>
    <REDACTED>
builtins.ValueError: <REDACTED>"""
        assert stack_trace == expected_lines


def test_add_integration_error_log_with_log_collection_disabled(mock_time, telemetry_writer, test_agent_session):
    """Test that add_integration_error_log respects LOG_COLLECTION_ENABLED setting"""
    original_value = telemetry_config.LOG_COLLECTION_ENABLED
    try:
        telemetry_config.LOG_COLLECTION_ENABLED = False

        try:
            raise ValueError("Test exception")
        except ValueError as e:
            telemetry_writer.add_error_log("Test error message", e)
            telemetry_writer.periodic(force_flush=True)

            log_events = test_agent_session.get_events("logs")
            assert len(log_events) == 0
    finally:
        telemetry_config.LOG_COLLECTION_ENABLED = original_value


@pytest.mark.parametrize(
    "filename, result",
    [
        ("/path/to/file.py", "<REDACTED>"),
        ("/path/to/ddtrace/contrib/flask/file.py", "<REDACTED>"),
        ("/path/to/lib/python3.13/site-packages/ddtrace/_trace/tracer.py", "ddtrace/_trace/tracer.py"),
        ("/path/to/lib/python3.13/site-packages/requests/api.py", "requests/api.py"),
        (
            "/path/to/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/json/__init__.py",
            "json/__init__.py",
        ),
    ],
)
def test_redact_filename(filename, result):
    """Test file redaction logic"""
    writer = TelemetryWriter(is_periodic=False)
    assert writer._format_file_path(filename) == result


def test_telemetry_writer_multiple_sources_config(telemetry_writer, test_agent_session):
    """Test that telemetry data is submitted for multiple sources with increasing seq_id"""

    telemetry_writer.add_configuration("DD_SERVICE", "unamed_python_service", "default")
    telemetry_writer.add_configuration("DD_SERVICE", "otel_service", "otel_env_var")
    telemetry_writer.add_configuration("DD_SERVICE", "dd_service", "env_var")
    telemetry_writer.add_configuration("DD_SERVICE", "monkey", "code")
    telemetry_writer.add_configuration("DD_SERVICE", "baboon", "remote_config")
    telemetry_writer.add_configuration("DD_SERVICE", "baboon", "fleet_stable_config")

    telemetry_writer.periodic(force_flush=True)

    configs = test_agent_session.get_configurations(name="DD_SERVICE", remove_seq_id=False, effective=False)
    assert len(configs) == 6, configs

    sorted_configs = sorted(configs, key=lambda x: x["seq_id"])
    assert sorted_configs[0]["value"] == "unamed_python_service"
    assert sorted_configs[0]["origin"] == "default"
    assert sorted_configs[0]["seq_id"] == 1

    assert sorted_configs[1]["value"] == "otel_service"
    assert sorted_configs[1]["origin"] == "otel_env_var"
    assert sorted_configs[1]["seq_id"] == 2

    assert sorted_configs[2]["value"] == "dd_service"
    assert sorted_configs[2]["origin"] == "env_var"
    assert sorted_configs[2]["seq_id"] == 3

    assert sorted_configs[3]["value"] == "monkey"
    assert sorted_configs[3]["origin"] == "code"
    assert sorted_configs[3]["seq_id"] == 4

    assert sorted_configs[4]["value"] == "baboon"
    assert sorted_configs[4]["origin"] == "remote_config"
    assert sorted_configs[4]["seq_id"] == 5

    assert sorted_configs[5]["value"] == "baboon"
    assert sorted_configs[5]["origin"] == "fleet_stable_config"
    assert sorted_configs[5]["seq_id"] == 6
