package tidbcloud

import (
	"testing"

	"github.com/Kong/go-pdk"
	"github.com/Kong/go-pdk/bridge"
	"github.com/Kong/go-pdk/bridge/bridgetest"
	"github.com/Kong/go-pdk/request"
)

const testOrgID = "1372813089201051283"

const testSessionID = "66aa3e4d0dab3e277cb59cb13973f880768f827d0036c5122ce6538f93af4c56"

func TestHashOrgID(t *testing.T) {
	if got := hashOrgID("  " + testOrgID + "\n"); got != testSessionID {
		t.Fatalf("hashOrgID() = %q, want %q", got, testSessionID)
	}
}

//func TestGetHashOrgID(t *testing.T) {
//	orgID := "1369847559691517301"
//	got := hashOrgID("  " + orgID + "\n")
//	t.Log(got)
//}

func TestValidateOrgClusterMatchesHashedSessionID(t *testing.T) {
	kong := newOrgClusterTestPDK(t, testSessionID, "/api/v1/orgs/"+testOrgID+"/data-proxy/clusters")

	validation, err := ValidateOrgCluster(kong, true)
	if err != nil {
		t.Fatalf("ValidateOrgCluster() error = %v", err)
	}
	if !validation.Checked || validation.SessionID != testSessionID {
		t.Fatalf("ValidateOrgCluster() = %+v, want checked hashed session ID", validation)
	}
}

func TestValidateOrgClusterRejectsMismatchedHashedSessionID(t *testing.T) {
	otherSessionID := hashOrgID("123")
	kong := newOrgClusterTestPDK(t, otherSessionID, "/api/v1/orgs/"+testOrgID+"/data-proxy/clusters")

	validation, err := ValidateOrgCluster(kong, true)
	if err == nil {
		t.Fatal("ValidateOrgCluster() error = nil, want org mismatch")
	}
	if !validation.Checked || validation.SessionID != otherSessionID {
		t.Fatalf("ValidateOrgCluster() = %+v, want checked mismatched session ID", validation)
	}
}

func TestValidateOrgClusterRejectsInvalidSessionID(t *testing.T) {
	kong := newOrgClusterHeaderTestPDK(t, "1372813089201051283")

	validation, err := ValidateOrgCluster(kong, true)
	if err == nil {
		t.Fatal("ValidateOrgCluster() error = nil, want invalid x-session-id")
	}
	if validation.Checked {
		t.Fatalf("ValidateOrgCluster() = %+v, want unchecked invalid session ID", validation)
	}
}

func newOrgClusterTestPDK(t *testing.T, sessionID, path string) *pdk.PDK {
	t.Helper()

	pdkBridge := bridge.New(bridgetest.Mock(t, []bridgetest.MockStep{
		{Method: "kong.request.get_header", Args: bridge.WrapString(sessionIDHeader), Ret: bridge.WrapString(sessionID)},
		{Method: "kong.request.get_path", Ret: bridge.WrapString(path)},
	}))
	return &pdk.PDK{Request: request.Request{PdkBridge: pdkBridge}}
}

func newOrgClusterHeaderTestPDK(t *testing.T, sessionID string) *pdk.PDK {
	t.Helper()

	pdkBridge := bridge.New(bridgetest.Mock(t, []bridgetest.MockStep{
		{Method: "kong.request.get_header", Args: bridge.WrapString(sessionIDHeader), Ret: bridge.WrapString(sessionID)},
	}))
	return &pdk.PDK{Request: request.Request{PdkBridge: pdkBridge}}
}

func TestRequestTraceFromHeadersPrefersTraceparent(t *testing.T) {
	trace := requestTraceFromHeaders(map[string][]string{
		"x-trace-id":  {"11111111111111111111111111111111"},
		"traceparent": {"00-22222222222222222222222222222222-3333333333333333-01"},
	})
	if trace.ID != "22222222222222222222222222222222" || trace.Source != traceParentHeader {
		t.Fatalf("requestTraceFromHeaders() = %+v, want traceparent trace", trace)
	}
}

func TestRequestTraceFromHeadersRejectsNonTraceRequestID(t *testing.T) {
	trace := requestTraceFromHeaders(map[string][]string{
		"x-kong-request-id": {"not-an-opentelemetry-trace-id"},
	})
	if trace.ID != "missing" {
		t.Fatalf("requestTraceFromHeaders() = %+v, want missing", trace)
	}
}

func TestRequestTraceFromHeadersRejectsInvalidTraceparent(t *testing.T) {
	trace := requestTraceFromHeaders(map[string][]string{
		"traceparent": {"00-not-a-valid-trace-id-3333333333333333-01"},
	})
	if trace.ID != "missing" {
		t.Fatalf("requestTraceFromHeaders() = %+v, want missing", trace)
	}
}

func TestRequestTraceFromHeadersIgnoresDebugTraceID(t *testing.T) {
	trace := requestTraceFromHeaders(map[string][]string{
		"x-debug-trace-id": {"11111111111111111111111111111111"},
		"x-trace-id":       {"22222222222222222222222222222222"},
	})
	if trace.ID != "22222222222222222222222222222222" || trace.Source != traceIDHeader {
		t.Fatalf("requestTraceFromHeaders() = %+v, want X-Trace-ID trace", trace)
	}
}

func TestOrgIDFromPath(t *testing.T) {
	tests := []struct {
		name    string
		path    string
		wantID  uint64
		wantHas bool
		wantErr bool
	}{
		{
			name:    "data proxy org path",
			path:    "/api/v1/orgs/123/data-proxy/clusters",
			wantID:  123,
			wantHas: true,
		},
		{
			name:    "ngm org path",
			path:    "/api/v1/orgs/456/projects/789/clusters/999/ngm/slow_query/list",
			wantID:  456,
			wantHas: true,
		},
		{
			name:    "no org path",
			path:    "/healthz",
			wantHas: false,
		},
		{
			name:    "invalid org path",
			path:    "/api/v1/orgs/not-a-number/data-proxy/clusters",
			wantHas: true,
			wantErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			gotID, gotHas, err := orgIDFromPath(test.path)
			if (err != nil) != test.wantErr {
				t.Fatalf("orgIDFromPath(%q) error = %v, wantErr %t", test.path, err, test.wantErr)
			}
			if gotID != test.wantID || gotHas != test.wantHas {
				t.Fatalf("orgIDFromPath(%q) = (%d, %t), want (%d, %t)", test.path, gotID, gotHas, test.wantID, test.wantHas)
			}
		})
	}
}

func TestClusterIDFromPath(t *testing.T) {
	if got := clusterIDFromPath("/api/v1/orgs/123/data-proxy/clusters/456/query"); got != "456" {
		t.Fatalf("clusterIDFromPath() = %q, want 456", got)
	}
	if got := clusterIDFromPath("/api/v1/orgs/123/data-proxy/clusters"); got != "" {
		t.Fatalf("clusterIDFromPath() = %q, want empty", got)
	}
}
