package clinic_internal_api

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

func TestGetClusterDataURLs(t *testing.T) {
	// Mock response
	mockResponse := &ClusterDataURLs{
		ClusterName:     "test-cluster",
		ClusterID:       "123456",
		OrgID:           "1000",
		ProjectID:       "proj-001",
		Region:          "us-west-2",
		DeployType:      "dedicated",
		Provider:        "aws",
		CreateTime:      1234567890,
		Status:          "ACTIVE",
		LokiURL:         "https://loki.example.com/loki/self-monitoring",
		StatementsS3URL: "s3://example/deltalake/org/o11y/persisted_statements_summary",
	}

	// Create mock server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/clinic/api/v1/internal/clusters/123456/cluster-dataurls", r.URL.Path)
		assert.Equal(t, http.MethodGet, r.Method)

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(mockResponse)
	}))
	defer server.Close()

	// Create client
	client := NewClient(server.URL)

	// Test successful request
	result, err := client.GetClusterDataURLs(context.Background(), "123456")
	require.NoError(t, err)
	assert.Equal(t, "test-cluster", result.ClusterName)
	assert.Equal(t, "123456", result.ClusterID)
	assert.Equal(t, "https://loki.example.com/loki/self-monitoring", result.LokiURL)
	assert.Equal(t, "s3://example/deltalake/org/o11y/persisted_statements_summary", result.StatementsS3URL)
}

func TestGetClusterDataURLsPropagatesTraceID(t *testing.T) {
	originalPropagator := otel.GetTextMapPropagator()
	t.Cleanup(func() { otel.SetTextMapPropagator(originalPropagator) })
	clinictracing.ConfigurePropagation()

	receivedHeader := make(chan http.Header, 1)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedHeader <- r.Header.Clone()
		w.Header().Set("Content-Type", "application/json")
		_, _ = io.WriteString(w, `{}`)
	}))
	defer server.Close()

	traceID := trace.TraceID{0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}
	spanContext := trace.NewSpanContext(trace.SpanContextConfig{
		TraceID:    traceID,
		SpanID:     trace.SpanID{1},
		TraceFlags: trace.FlagsSampled,
	})
	ctx := trace.ContextWithSpanContext(context.Background(), spanContext)

	_, err := NewClient(server.URL).GetClusterDataURLs(ctx, "123456")
	require.NoError(t, err)
	header := <-receivedHeader
	require.Equal(t, traceID.String(), header.Get(clinictracing.TraceIDHeader))
	extracted := propagation.TraceContext{}.Extract(context.Background(), propagation.HeaderCarrier(header))
	require.Equal(t, traceID, trace.SpanContextFromContext(extracted).TraceID())
}

func TestGetClusterDataURLs_EmptyClusterID(t *testing.T) {
	client := NewClient(DEFAULT_CLINIC_API_BASE)
	_, err := client.GetClusterDataURLs(context.Background(), "")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cluster_id is required")
}

func TestGetClusterDataURLs_NotFound(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("cluster not found"))
	}))
	defer server.Close()

	client := NewClient(server.URL)
	_, err := client.GetClusterDataURLs(context.Background(), "nonexistent")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "404")
}

func TestGetCanonicalClusterDataURLs(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		assert.Equal(t, "/clinic/api/v1/internal/clusters/123456/cluster-dataurls", r.URL.Path)
		assert.Equal(t, http.MethodGet, r.Method)
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(ClusterDataURLs{
			ClusterID: "123456",
			OrgID:     "1000",
			Provider:  "aws",
			Region:    "us-west-2",
		})
	}))
	defer server.Close()

	client := NewClient(server.URL)
	result, err := client.GetCanonicalClusterDataURLs(context.Background(), "123456")
	require.NoError(t, err)
	require.Equal(t, "123456", result.ClusterID)
	require.Equal(t, "1000", result.OrgID)
}

func TestGetCanonicalClusterDataURLsReturnsTypedErrorWithoutBody(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusGatewayTimeout)
		_, _ = io.WriteString(w, "internal-secret-response-body")
	}))
	defer server.Close()

	client := NewClient(server.URL)
	result, err := client.GetCanonicalClusterDataURLs(context.Background(), "123456")
	require.Nil(t, result)
	var upstreamError *UpstreamError
	require.ErrorAs(t, err, &upstreamError)
	require.Equal(t, http.StatusGatewayTimeout, upstreamError.StatusCode)
	require.NotContains(t, err.Error(), "internal-secret-response-body")
}

func TestGetCanonicalClusterDataURLsDoesNotFollowRedirects(t *testing.T) {
	redirectFollowed := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/redirected" {
			redirectFollowed = true
			_, _ = io.WriteString(w, `{}`)
			return
		}
		http.Redirect(w, r, "/redirected", http.StatusFound)
	}))
	defer server.Close()

	client := NewClient(server.URL)
	result, err := client.GetCanonicalClusterDataURLs(context.Background(), "123456")
	require.Nil(t, result)
	var upstreamError *UpstreamError
	require.ErrorAs(t, err, &upstreamError)
	require.Equal(t, http.StatusFound, upstreamError.StatusCode)
	require.False(t, redirectFollowed)
}

func TestGetCanonicalClusterDataURLsRejectsOversizedResponse(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = io.CopyN(w, zeroReader{}, int64(maxResourceResponseBytes+1))
	}))
	defer server.Close()

	client := NewClient(server.URL)
	result, err := client.GetCanonicalClusterDataURLs(context.Background(), "123456")
	require.Nil(t, result)
	require.ErrorContains(t, err, "response exceeds")
}

func TestNewClient_DefaultBaseURL(t *testing.T) {
	client := NewClient("")
	assert.Equal(t, DEFAULT_CLINIC_API_BASE, client.GetBaseURL())
}

func TestNewClientWithTimeout(t *testing.T) {
	client := NewClientWithTimeout("http://example.com", 5*time.Second)
	assert.Equal(t, "http://example.com", client.GetBaseURL())
	assert.Equal(t, 5*time.Second, client.httpClient.Timeout)
}
