package tracing

import (
	"context"
	"net"
	"net/http"
	"reflect"
	"testing"

	"go.opentelemetry.io/otel/attribute"
	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc/peer"

	"github.com/go-kratos/kratos/v2/internal/testdata/binding"
	"github.com/go-kratos/kratos/v2/metadata"
	"github.com/go-kratos/kratos/v2/transport"
)

func Test_parseFullMethod(t *testing.T) {
	tests := []struct {
		name       string
		fullMethod string
		want       string
		wantAttr   []attribute.KeyValue
	}{
		{
			name:       "/foo.bar/hello",
			fullMethod: "/foo.bar/hello",
			want:       "foo.bar/hello",
			wantAttr: []attribute.KeyValue{
				semconv.RPCServiceKey.String("foo.bar"),
				semconv.RPCMethodKey.String("hello"),
			},
		},
		{
			name:       "/foo.bar/hello/world",
			fullMethod: "/foo.bar/hello/world",
			want:       "foo.bar/hello/world",
			wantAttr: []attribute.KeyValue{
				semconv.RPCServiceKey.String("foo.bar"),
				semconv.RPCMethodKey.String("hello/world"),
			},
		},
		{
			name:       "/hello",
			fullMethod: "/hello",
			want:       "hello",
			wantAttr:   []attribute.KeyValue{attribute.Key("rpc.operation").String("/hello")},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, got1 := parseFullMethod(tt.fullMethod)
			if got != tt.want {
				t.Errorf("parseFullMethod() got = %v, want %v", got, tt.want)
			}
			if !reflect.DeepEqual(got1, tt.wantAttr) {
				t.Errorf("parseFullMethod() got1 = %v, want %v", got1, tt.wantAttr)
			}
		})
	}
}

func Test_peerAttr(t *testing.T) {
	tests := []struct {
		name string
		addr string
		want []attribute.KeyValue
	}{
		{
			name: "nil addr",
			addr: ":8080",
			want: []attribute.KeyValue{
				semconv.NetPeerIPKey.String("127.0.0.1"),
				semconv.NetPeerPortKey.String("8080"),
			},
		},
		{
			name: "normal addr without port",
			addr: "192.168.0.1",
			want: []attribute.KeyValue(nil),
		},
		{
			name: "normal addr with port",
			addr: "192.168.0.1:8080",
			want: []attribute.KeyValue{
				semconv.NetPeerIPKey.String("192.168.0.1"),
				semconv.NetPeerPortKey.String("8080"),
			},
		},
		{
			name: "dns addr",
			addr: "foo:8080",
			want: []attribute.KeyValue{
				semconv.NetPeerIPKey.String("foo"),
				semconv.NetPeerPortKey.String("8080"),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := peerAttr(tt.addr); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("peerAttr() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_parseTarget(t *testing.T) {
	tests := []struct {
		name        string
		endpoint    string
		wantAddress string
		wantErr     bool
	}{
		{
			name:        "http",
			endpoint:    "http://foo.bar:8080",
			wantAddress: "http://foo.bar:8080",
			wantErr:     false,
		},
		{
			name:        "http",
			endpoint:    "http://127.0.0.1:8080",
			wantAddress: "http://127.0.0.1:8080",
			wantErr:     false,
		},
		{
			name:        "without protocol",
			endpoint:    "foo.bar:8080",
			wantAddress: "foo.bar:8080",
			wantErr:     false,
		},
		{
			name:        "grpc",
			endpoint:    "grpc://foo.bar",
			wantAddress: "grpc://foo.bar",
			wantErr:     false,
		},
		{
			name:        "with path",
			endpoint:    "/foo",
			wantAddress: "foo",
			wantErr:     false,
		},
		{
			name:        "with path",
			endpoint:    "http://127.0.0.1/hello",
			wantAddress: "hello",
			wantErr:     false,
		},
		{
			name:        "empty",
			endpoint:    "%%",
			wantAddress: "",
			wantErr:     true,
		},
		{
			name:        "invalid path",
			endpoint:    "//%2F/#%2Fanother",
			wantAddress: "",
			wantErr:     false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotAddress, err := parseTarget(tt.endpoint)
			if (err != nil) != tt.wantErr {
				t.Errorf("parseTarget() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if gotAddress != tt.wantAddress {
				t.Errorf("parseTarget() = %v, want %v", gotAddress, tt.wantAddress)
			}
		})
	}
}

func TestSetServerSpan(_ *testing.T) {
	ctx := context.Background()
	_, span := trace.NewNoopTracerProvider().Tracer("Tracer").Start(ctx, "Spanname")

	// Handle without Transport context
	setServerSpan(ctx, span, nil)

	// Handle with proto message
	m := &binding.HelloRequest{}
	setServerSpan(ctx, span, m)

	// Handle with metadata context
	ctx = metadata.NewServerContext(ctx, metadata.New())
	setServerSpan(ctx, span, m)

	// Handle with KindHTTP transport context
	mt := &mockTransport{
		kind: transport.KindHTTP,
	}
	mt.request, _ = http.NewRequest(http.MethodGet, "/endpoint", nil)
	ctx = transport.NewServerContext(ctx, mt)
	setServerSpan(ctx, span, m)

	// Handle with KindGRPC transport context
	mt.kind = transport.KindGRPC
	ctx = transport.NewServerContext(ctx, mt)
	ip, _ := net.ResolveIPAddr("ip", "1.1.1.1")
	ctx = peer.NewContext(ctx, &peer.Peer{
		Addr: ip,
	})
	setServerSpan(ctx, span, m)
}

func TestSetClientSpan(_ *testing.T) {
	ctx := context.Background()
	_, span := trace.NewNoopTracerProvider().Tracer("Tracer").Start(ctx, "Spanname")

	// Handle without Transport context
	setClientSpan(ctx, span, nil)

	// Handle with proto message
	m := &binding.HelloRequest{}
	setClientSpan(ctx, span, m)

	// Handle with metadata context
	ctx = metadata.NewClientContext(ctx, metadata.New())
	setClientSpan(ctx, span, m)

	// Handle with KindHTTP transport context
	mt := &mockTransport{
		kind: transport.KindHTTP,
	}
	mt.request, _ = http.NewRequest(http.MethodGet, "/endpoint", nil)
	mt.request.Host = "MyServer"
	ctx = transport.NewClientContext(ctx, mt)
	setClientSpan(ctx, span, m)

	// Handle with KindGRPC transport context
	mt.kind = transport.KindGRPC
	ctx = transport.NewClientContext(ctx, mt)
	ip, _ := net.ResolveIPAddr("ip", "1.1.1.1")
	ctx = peer.NewContext(ctx, &peer.Peer{
		Addr: ip,
	})
	setClientSpan(ctx, span, m)

	// Handle without Host request
	ctx = transport.NewClientContext(ctx, mt)
	setClientSpan(ctx, span, m)
}