package tracing

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"

	"github.com/aliyun/credentials-go/credentials"
	"github.com/stretchr/testify/require"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

func TestInstrumentDefaultHTTPClientPreservesAliyunOIDCAndTracePropagation(t *testing.T) {
	originalDefaultTransport := http.DefaultTransport
	originalDefaultClient := http.DefaultClient
	originalPropagator := otel.GetTextMapPropagator()
	// This test mutates process-wide HTTP and OTel state and must not run in
	// parallel. Reset the once guard so repeated test runs remain isolated.
	t.Cleanup(func() {
		http.DefaultTransport = originalDefaultTransport
		http.DefaultClient = originalDefaultClient
		otel.SetTextMapPropagator(originalPropagator)
		instrumentDefaultHTTPClientOnce = sync.Once{}
	})

	type stsRequestValues struct {
		action string
		token  string
		err    error
	}
	stsRequest := make(chan stsRequestValues, 1)
	stsServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		parseErr := r.ParseForm()
		stsRequest <- stsRequestValues{
			action: r.Form.Get("Action"),
			token:  r.Form.Get("OIDCToken"),
			err:    parseErr,
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = io.WriteString(w, `{"Credentials":{"AccessKeyId":"akid","AccessKeySecret":"aksecret","Expiration":"2099-10-20T04:27:09Z","SecurityToken":"ststoken"}}`)
	}))
	t.Cleanup(stsServer.Close)

	stsTransport, ok := stsServer.Client().Transport.(*http.Transport)
	require.True(t, ok)
	http.DefaultTransport = stsTransport.Clone()
	http.DefaultClient = &http.Client{}

	ConfigurePropagation()
	InstrumentDefaultHTTPClient()

	defaultTransport, ok := http.DefaultTransport.(*http.Transport)
	require.True(t, ok, "Alibaba credentials-go requires a concrete *http.Transport")
	require.NotPanics(t, func() { _ = defaultTransport.Clone() })

	tokenPath := filepath.Join(t.TempDir(), "oidc-token")
	require.NoError(t, os.WriteFile(tokenPath, []byte("test-oidc-token"), 0o600))
	credential, err := credentials.NewCredential(
		new(credentials.Config).
			SetType("oidc_role_arn").
			SetOIDCProviderArn("acs:ram::123456789:oidc-provider/test").
			SetRoleArn("acs:ram::123456789:role/test").
			SetOIDCTokenFilePath(tokenPath).
			SetRoleSessionName("clinic-test").
			SetRoleSessionExpiration(900).
			SetSTSEndpoint(strings.TrimPrefix(stsServer.URL, "https://")),
	)
	require.NoError(t, err)

	var credentialModel *credentials.CredentialModel
	require.NotPanics(t, func() {
		credentialModel, err = credential.GetCredential()
	})
	require.NoError(t, err)
	require.NotNil(t, credentialModel)
	require.NotNil(t, credentialModel.AccessKeyId)
	require.NotNil(t, credentialModel.SecurityToken)
	require.Equal(t, "akid", *credentialModel.AccessKeyId)
	require.Equal(t, "ststoken", *credentialModel.SecurityToken)

	requestValues := <-stsRequest
	require.NoError(t, requestValues.err)
	require.Equal(t, "AssumeRoleWithOIDC", requestValues.action)
	require.Equal(t, "test-oidc-token", requestValues.token)

	traceHeader := make(chan http.Header, 1)
	traceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		traceHeader <- r.Header.Clone()
		w.WriteHeader(http.StatusNoContent)
	}))
	t.Cleanup(traceServer.Close)

	traceID := trace.TraceID{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}
	spanContext := trace.NewSpanContext(trace.SpanContextConfig{
		TraceID:    traceID,
		SpanID:     trace.SpanID{1},
		TraceFlags: trace.FlagsSampled,
	})
	req, err := http.NewRequestWithContext(
		trace.ContextWithSpanContext(context.Background(), spanContext),
		http.MethodGet,
		traceServer.URL,
		nil,
	)
	require.NoError(t, err)
	resp, err := http.DefaultClient.Do(req)
	require.NoError(t, err)
	require.NoError(t, resp.Body.Close())

	headers := <-traceHeader
	require.Equal(t, traceID.String(), headers.Get(TraceIDHeader))
	extracted := propagation.TraceContext{}.Extract(context.Background(), propagation.HeaderCarrier(headers))
	require.Equal(t, traceID, trace.SpanContextFromContext(extracted).TraceID())
}
