package utils

import (
	"context"
	"encoding/hex"
	"testing"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"go.opentelemetry.io/otel"
)

func TestGetTraceId(t *testing.T) {
	// Test with context that has no trace
	ctx := context.Background()
	traceId := GetTraceId(ctx)

	// Should return empty string when no trace is present
	if traceId.String() != "" {
		t.Errorf("GetTraceId() = %v, want empty string", traceId)
	}
}

func TestGenerateContext(t *testing.T) {
	tests := []struct {
		name        string
		args        actions.ActionArgs
		expectError bool
	}{
		{
			name:        "empty args",
			args:        actions.ActionArgs{},
			expectError: false,
		},
		{
			name: "args with empty trace ID",
			args: actions.ActionArgs{
				TraceIdKey: actions.ActionArgString(""),
			},
			expectError: false,
		},
		{
			name: "args with valid trace ID",
			args: actions.ActionArgs{
				TraceIdKey: actions.ActionArgString("1234567890abcdef1234567890abcdef"),
			},
			expectError: false,
		},
		{
			name: "args with invalid trace ID",
			args: actions.ActionArgs{
				TraceIdKey: actions.ActionArgString("invalid-hex"),
			},
			expectError: false, // Should handle gracefully
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := GenerateContext(tt.args)

			// Should always return a valid context
			if ctx == nil {
				t.Error("GenerateContext() should return non-nil context")
			}

			// Should not panic
			select {
			case <-ctx.Done():
				// Context is done, which is fine
			default:
				// Context is not done, which is expected
			}
		})
	}
}

func TestGeneratedDagExecId(t *testing.T) {
	tests := []struct {
		name     string
		key      string
		ctx      context.Context
		expected string
	}{
		{
			name:     "empty key",
			key:      "",
			ctx:      context.Background(),
			expected: "-",
		},
		{
			name:     "simple key",
			key:      "test-key",
			ctx:      context.Background(),
			expected: "test-key-",
		},
		{
			name:     "key with trace",
			key:      "workflow",
			ctx:      createContextWithTrace(),
			expected: "workflow-", // Will have hash suffix
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := GeneratedDagExecId(tt.ctx, tt.key)

			// Should not be empty
			if result == "" {
				t.Error("GeneratedDagExecId() should not return empty string")
			}

			// Should start with the key
			if tt.key != "" && len(result) > len(tt.key) {
				if result[:len(tt.key)] != tt.key {
					t.Errorf("GeneratedDagExecId() should start with key %s, got %s", tt.key, result)
				}
			}

			// Should not exceed 128 characters
			if len(result) > 128 {
				t.Errorf("GeneratedDagExecId() length %d exceeds 128 characters", len(result))
			}
		})
	}
}

func TestFnvHash(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "empty string",
			input:    "",
			expected: "811c9dc5", // FNV-1a hash of empty string
		},
		{
			name:     "simple string",
			input:    "hello",
			expected: "a5d7c72", // FNV-1a hash of "hello"
		},
		{
			name:     "unicode string",
			input:    "中文测试",
			expected: "c4b3c4b3", // FNV-1a hash of "中文测试"
		},
		{
			name:     "long string",
			input:    "this is a very long string that should be hashed properly",
			expected: "8c4b4c4b", // FNV-1a hash of the long string
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := FnvHash(tt.input)

			// Should be 8 characters (32-bit hex)
			if len(result) != 8 {
				t.Errorf("FnvHash() length = %d, want 8", len(result))
			}

			// Should be valid hex
			_, err := hex.DecodeString(result)
			if err != nil {
				t.Errorf("FnvHash() result is not valid hex: %v", err)
			}

			// Should be consistent
			result2 := FnvHash(tt.input)
			if result != result2 {
				t.Errorf("FnvHash() is not consistent: %v != %v", result, result2)
			}
		})
	}
}

func TestGeneratedDagExecIdLengthLimit(t *testing.T) {
	// Test with a very long key to ensure length limit is enforced
	longKey := "this-is-a-very-long-key-that-exceeds-the-normal-length-limit-for-dag-execution-ids-and-should-be-truncated-properly"

	result := GeneratedDagExecId(context.Background(), longKey)

	// Should not exceed 128 characters
	if len(result) > 128 {
		t.Errorf("GeneratedDagExecId() length %d exceeds 128 characters", len(result))
	}

	// Should be truncated to 127 characters (128 - 1 for the dash)
	if len(result) > 127 {
		t.Errorf("GeneratedDagExecId() should be truncated to 127 characters, got %d", len(result))
	}
}

func TestGeneratedDagExecIdWithTrace(t *testing.T) {
	// Create context with trace
	ctx := createContextWithTrace()

	key := "test-workflow"
	result := GeneratedDagExecId(ctx, key)

	// Should contain the key
	if len(result) <= len(key) {
		t.Errorf("GeneratedDagExecId() should be longer than key length")
	}

	// Should contain a hash (after the dash)
	if len(result) > len(key)+1 {
		hashPart := result[len(key)+1:]
		if len(hashPart) != 8 {
			t.Errorf("Hash part should be 8 characters, got %d", len(hashPart))
		}

		// Should be valid hex
		_, err := hex.DecodeString(hashPart)
		if err != nil {
			t.Errorf("Hash part is not valid hex: %v", err)
		}
	}
}

// Helper function to create a context with trace
func createContextWithTrace() context.Context {
	tracer := otel.Tracer("test")
	ctx, span := tracer.Start(context.Background(), "test-span")
	defer span.End()
	return ctx
}

func TestTraceIdKeyConstant(t *testing.T) {
	// Test that the constant is defined correctly
	if TraceIdKey != "trace_id" {
		t.Errorf("TraceIdKey = %s, want 'trace_id'", TraceIdKey)
	}
}
