package dataplane

import (
	"context"
	"sync"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var _ EnvProvider = &FakeEnvProvider{}

type FakeEnvProvider struct {
	Client client.Client
	*provider
}

func (f *FakeEnvProvider) GetClient() client.Client {
	return f.Client
}

var _ EnvProviderManager = &fakeEnvProviderManager{}

type fakeEnvProviderManager struct {
	mutex                sync.Mutex
	m                    map[string]EnvProvider
	scheme               *runtime.Scheme
	envProviderGenerator func(ctx context.Context, scheme *runtime.Scheme, cluster *cloudv1.Cluster) (EnvProvider, error)
}

func NewFakeEnvProviderManager(
	scheme *runtime.Scheme,
	envProviderGenerator func(ctx context.Context, scheme *runtime.Scheme, cluster *cloudv1.Cluster) (EnvProvider, error),
) *fakeEnvProviderManager {
	mgr := &fakeEnvProviderManager{
		m:                    make(map[string]EnvProvider),
		scheme:               scheme,
		envProviderGenerator: envProviderGenerator,
	}
	if mgr.envProviderGenerator == nil {
		mgr.envProviderGenerator = mgr.defaultEnvProviderGenerator
	}
	return mgr
}

// Deregister implements EnvProviderManager.
func (f *fakeEnvProviderManager) Deregister(ctx context.Context, cluster *cloudv1.Cluster) {
	delete(f.m, cluster.Name)
}

// GetClient implements EnvProviderManager.
func (f *fakeEnvProviderManager) GetClient(ctx context.Context, cluster *cloudv1.Cluster) (client.Client, error) {
	envProvider, err := f.GetEnvProvider(ctx, cluster)
	if err != nil {
		return nil, err
	}
	return envProvider.GetClient(), nil
}

// GetEnvProvider implements EnvProviderManager.
func (f *fakeEnvProviderManager) GetEnvProvider(ctx context.Context, cluster *cloudv1.Cluster) (EnvProvider, error) {
	f.mutex.Lock()
	defer f.mutex.Unlock()

	if envProvider, ok := f.m[cluster.Name]; ok {
		return envProvider, nil
	}
	envProvider, err := f.envProviderGenerator(ctx, f.scheme, cluster)
	if err != nil {
		return nil, err
	}
	f.m[cluster.Name] = envProvider
	return envProvider, nil
}

func (f *fakeEnvProviderManager) defaultEnvProviderGenerator(ctx context.Context, scheme *runtime.Scheme, cluster *cloudv1.Cluster) (EnvProvider, error) {
	return &FakeEnvProvider{
		Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
		provider: &provider{
			name:   cluster.Name,
			k:      &cluster.Spec.Kubernetes,
			m:      nil,
			region: cluster.GetRegion(),
		},
	}, nil
}
