package trace

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/zeromicro/go-zero/core/logx"
	"go.opentelemetry.io/otel"
)

func TestStartAgent(t *testing.T) {
	logx.Disable()

	const (
		endpoint1  = "localhost:1234"
		endpoint2  = "remotehost:1234"
		endpoint3  = "localhost:1235"
		endpoint4  = "localhost:1236"
		endpoint5  = "udp://localhost:6831"
		endpoint6  = "localhost:1237"
		endpoint71 = "/tmp/trace.log"
		endpoint72 = "/not-exist-fs/trace.log"
	)
	c1 := Config{
		Name: "foo",
	}
	c2 := Config{
		Name:     "any",
		Endpoint: endpoint2,
		Batcher:  kindZipkin,
	}
	c3 := Config{
		Name:     "bla",
		Endpoint: endpoint3,
		Batcher:  "otlp",
	}
	c4 := Config{
		Name:     "otlpgrpc",
		Endpoint: endpoint3,
		Batcher:  kindOtlpGrpc,
		OtlpHeaders: map[string]string{
			"uptrace-dsn": "http://project2_secret_token@localhost:14317/2",
		},
	}
	c5 := Config{
		Name:     "otlphttp",
		Endpoint: endpoint4,
		Batcher:  kindOtlpHttp,
		OtlpHeaders: map[string]string{
			"uptrace-dsn": "http://project2_secret_token@localhost:14318/2",
		},
		OtlpHttpPath: "/v1/traces",
	}
	c6 := Config{
		Name:     "file",
		Endpoint: endpoint71,
		Batcher:  kindFile,
	}
	c7 := Config{
		Name:     "file",
		Endpoint: endpoint72,
		Batcher:  kindFile,
	}

	StartAgent(c1)
	StartAgent(c1)
	StartAgent(c2)
	StartAgent(c3)
	StartAgent(c4)
	StartAgent(c5)
	StartAgent(c6)
	StartAgent(c7)
	defer StopAgent()

	// With sync.Once, only the first non-disabled config (c1) takes effect.
	// Subsequent calls are ignored, which is the desired behavior to prevent
	// multiple servers (REST + RPC) from reinitializing the global tracer.
	assert.NotNil(t, tp)
}

func TestCreateExporter_InvalidFilePath(t *testing.T) {
	logx.Disable()

	c := Config{
		Name:     "test-invalid-file",
		Endpoint: "/non-existent-directory/trace.log",
		Batcher:  kindFile,
	}

	_, err := createExporter(c)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "file exporter endpoint error")
}

func TestCreateExporter_UnknownBatcher(t *testing.T) {
	logx.Disable()

	c := Config{
		Name:     "test-unknown",
		Endpoint: "localhost:1234",
		Batcher:  "unknown-batcher-type",
	}

	_, err := createExporter(c)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "unknown exporter")
}

func TestCreateExporter_ValidExporters(t *testing.T) {
	logx.Disable()

	tests := []struct {
		name    string
		config  Config
		wantErr bool
		errMsg  string
	}{
		{
			name: "valid file exporter",
			config: Config{
				Name:     "file-test",
				Endpoint: "/tmp/trace-test.log",
				Batcher:  kindFile,
			},
			wantErr: false,
		},
		{
			name: "invalid file path",
			config: Config{
				Name:     "file-test-invalid",
				Endpoint: "/invalid-path/that/does/not/exist/trace.log",
				Batcher:  kindFile,
			},
			wantErr: true,
			errMsg:  "file exporter endpoint error",
		},
		{
			name: "unknown batcher",
			config: Config{
				Name:     "unknown-test",
				Endpoint: "localhost:1234",
				Batcher:  "invalid-batcher",
			},
			wantErr: true,
			errMsg:  "unknown exporter",
		},
		{
			name: "zipkin",
			config: Config{
				Name:     "zipkin",
				Endpoint: "http://localhost:9411/api/v2/spans",
				Batcher:  kindZipkin,
			},
			wantErr: false,
		},
		{
			name: "otlpgrpc",
			config: Config{
				Name:     "otlpgrpc",
				Endpoint: "localhost:4317",
				Batcher:  kindOtlpGrpc,
			},
			wantErr: false,
		},
		{
			name: "otlpgrpc with headers",
			config: Config{
				Name:     "otlpgrpc-headers",
				Endpoint: "localhost:4317",
				Batcher:  kindOtlpGrpc,
				OtlpHeaders: map[string]string{
					"authorization": "Bearer token123",
					"x-custom-key":  "custom-value",
				},
			},
			wantErr: false,
		},
		{
			name: "otlphttp",
			config: Config{
				Name:     "otlphttp",
				Endpoint: "localhost:4318",
				Batcher:  kindOtlpHttp,
			},
			wantErr: false,
		},
		{
			name: "otlphttp with headers",
			config: Config{
				Name:     "otlphttp-headers",
				Endpoint: "localhost:4318",
				Batcher:  kindOtlpHttp,
				OtlpHeaders: map[string]string{
					"authorization": "Bearer token456",
					"x-api-key":     "api-key-value",
				},
			},
			wantErr: false,
		},
		{
			name: "otlphttp with headers and path",
			config: Config{
				Name:         "otlphttp-headers-path",
				Endpoint:     "localhost:4318",
				Batcher:      kindOtlpHttp,
				OtlpHttpPath: "/v1/traces",
				OtlpHeaders: map[string]string{
					"authorization":  "Bearer token789",
					"x-custom-trace": "trace-id",
				},
			},
			wantErr: false,
		},
		{
			name: "otlphttp with secure connection",
			config: Config{
				Name:           "otlphttp-secure",
				Endpoint:       "localhost:4318",
				Batcher:        kindOtlpHttp,
				OtlpHttpSecure: true,
				OtlpHeaders: map[string]string{
					"authorization": "Bearer secure-token",
				},
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			exporter, err := createExporter(tt.config)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, exporter)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, exporter)
				// Clean up the exporter
				if exporter != nil {
					_ = exporter.Shutdown(context.Background())
				}
			}
		})
	}
}

func TestStopAgent(t *testing.T) {
	logx.Disable()

	// StopAgent should be idempotent and safe to call multiple times
	assert.NotPanics(t, func() {
		StopAgent()
		StopAgent()
		StopAgent()
	})
}

func TestStartAgent_WithEndpoint(t *testing.T) {
	logx.Disable()

	tests := []struct {
		name    string
		config  Config
		wantErr bool
	}{
		{
			name: "empty endpoint - no exporter created",
			config: Config{
				Name:    "test-no-endpoint",
				Sampler: 1.0,
			},
			wantErr: false,
		},
		{
			name: "valid endpoint with file exporter",
			config: Config{
				Name:     "test-with-endpoint",
				Endpoint: "/tmp/test-trace.log",
				Batcher:  kindFile,
				Sampler:  1.0,
			},
			wantErr: false,
		},
		{
			name: "endpoint with invalid exporter type",
			config: Config{
				Name:     "test-invalid-batcher",
				Endpoint: "localhost:1234",
				Batcher:  "invalid-type",
				Sampler:  1.0,
			},
			wantErr: true,
		},
		{
			name: "endpoint with invalid file path",
			config: Config{
				Name:     "test-invalid-path",
				Endpoint: "/non/existent/path/trace.log",
				Batcher:  kindFile,
				Sampler:  1.0,
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Reset tp for each test
			originalTp := tp
			tp = nil
			defer func() {
				if tp != nil {
					_ = tp.Shutdown(context.Background())
				}
				tp = originalTp
			}()

			err := startAgent(tt.config)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, tp, "TracerProvider should be created")
			}
		})
	}
}

func TestStartAgent_ErrorHandler(t *testing.T) {
	// Setup a tracer provider to test error handler
	originalTp := tp
	tp = nil
	defer func() {
		if tp != nil {
			_ = tp.Shutdown(context.Background())
		}
		tp = originalTp
	}()

	// Call startAgent to set up the error handler
	config := Config{
		Name:    "test-error-handler",
		Sampler: 1.0,
	}
	err := startAgent(config)
	assert.NoError(t, err)
	assert.NotNil(t, tp)

	// Verify the error handler was set and can be called without panicking
	// We test this by calling otel.Handle which will invoke the registered error handler
	testErr := errors.New("test otel error")
	assert.NotPanics(t, func() {
		otel.Handle(testErr)
	}, "Error handler should handle errors without panicking")
}
