package main

import (
	"context"
	"errors"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/source/kubernetes"
)

type fakeHTTPServer struct {
	listenErr        error
	shutdownErr      error
	listenStartedCh  chan struct{}
	shutdownCalledCh chan struct{}
}

type contextMarkerKey struct{}

func newBlockingHTTPServer() *fakeHTTPServer {
	return &fakeHTTPServer{
		listenStartedCh:  make(chan struct{}),
		shutdownCalledCh: make(chan struct{}),
	}
}

func (s *fakeHTTPServer) ListenAndServe() error {
	if s.listenStartedCh != nil {
		close(s.listenStartedCh)
	}

	if s.listenErr != nil {
		return s.listenErr
	}

	<-s.shutdownCalledCh
	return http.ErrServerClosed
}

func (s *fakeHTTPServer) Shutdown(context.Context) error {
	if s.shutdownCalledCh != nil {
		close(s.shutdownCalledCh)
	}

	return s.shutdownErr
}

type fakeNodePodSource struct{}

func (f *fakeNodePodSource) ListNodes(context.Context) ([]kubernetes.Node, error) { return nil, nil }
func (f *fakeNodePodSource) ListPods(context.Context) ([]kubernetes.Pod, error)   { return nil, nil }
func (f *fakeNodePodSource) ListNamespaces(context.Context) ([]kubernetes.Namespace, error) {
	return nil, nil
}
func (f *fakeNodePodSource) ListResourceQuotas(context.Context) ([]kubernetes.ResourceQuota, error) {
	return nil, nil
}

func TestServeHTTPReturnsNilOnGracefulShutdown(t *testing.T) {
	server := newBlockingHTTPServer()
	ctx, cancel := context.WithCancel(context.Background())

	errCh := make(chan error, 1)
	go func() {
		errCh <- serveHTTP(ctx, server)
	}()

	<-server.listenStartedCh
	cancel()

	select {
	case err := <-errCh:
		require.NoError(t, err)
	case <-time.After(time.Second):
		t.Fatal("serveHTTP did not return after shutdown")
	}
}

func TestServeHTTPWrapsListenError(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := serveHTTP(ctx, &fakeHTTPServer{listenErr: errors.New("boom")})
	require.EqualError(t, err, "serve HTTP: boom")
}

func TestMustBuildExportersPassesContextToSourceBuilder(t *testing.T) {
	originalBuilder := buildSource
	defer func() {
		buildSource = originalBuilder
	}()

	ctx := context.WithValue(context.Background(), contextMarkerKey{}, "marker")
	buildSource = func(gotCtx context.Context) (kubernetes.NodePodSource, error) {
		require.Same(t, ctx, gotCtx)
		return &fakeNodePodSource{}, nil
	}

	exporters, err := mustBuildExporters(ctx)
	require.NoError(t, err)
	require.Len(t, exporters, 2)
}

func TestBuildExportersConfiguresControllerRuntimeLoggerBeforeBuildingSource(t *testing.T) {
	originalBuilder := buildSource
	originalSetLogger := setControllerRuntimeLogger
	defer func() {
		buildSource = originalBuilder
		setControllerRuntimeLogger = originalSetLogger
	}()

	var steps []string
	setControllerRuntimeLogger = func() {
		steps = append(steps, "logger")
	}
	buildSource = func(context.Context) (kubernetes.NodePodSource, error) {
		steps = append(steps, "source")
		return &fakeNodePodSource{}, nil
	}

	exporters, err := buildExporters(context.Background())
	require.NoError(t, err)
	require.Len(t, exporters, 2)
	require.Equal(t, []string{"logger", "source"}, steps)
}
