/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package providers

import (
	"fmt"
	"sync"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// CloudProviderRegistry maps CloudProvider enum to implementations
type CloudProviderRegistry struct {
	providers map[o11yv1.CloudProvider]CloudProviderInterface
}

var (
	cloudProviderRegistryOnce   sync.Once
	cloudProviderRegistryGlobal *CloudProviderRegistry
	cloudProviderRegistryErr    error
	cloudProviderRegistryRegion string
)

// NewCloudProviderRegistry returns a process-wide singleton registry for the given region.
// It initializes the registry only once (thread-safe). The region is taken from the first call.
// k8sClient is optional; when provided, GKE uses it to get actual node count from K8s in convertGKENodePoolToNodeGroup.
func NewCloudProviderRegistry(provider o11yv1.CloudProvider, region string, k8sClient client.Client) (*CloudProviderRegistry, error) {
	cloudProviderRegistryOnce.Do(func() {
		cloudProviderRegistryRegion = region
		registry := &CloudProviderRegistry{
			providers: make(map[o11yv1.CloudProvider]CloudProviderInterface),
		}

		switch provider {
		case o11yv1.CloudProviderEKS:
			eksProvider, err := NewEKSCloudProvider(region)
			if err != nil {
				cloudProviderRegistryErr = fmt.Errorf("failed to initialize EKS provider: %w", err)
				return
			}
			registry.Register(o11yv1.CloudProviderEKS, eksProvider)
		case o11yv1.CloudProviderGKE:
			gkeProvider, err := NewGKECloudProvider(region, k8sClient)
			if err != nil {
				cloudProviderRegistryErr = fmt.Errorf("failed to initialize GKE provider: %w", err)
				return
			}
			registry.Register(o11yv1.CloudProviderGKE, gkeProvider)
		case o11yv1.CloudProviderAKS:
			aksProvider, err := NewAKSCloudProvider(region)
			if err != nil {
				cloudProviderRegistryErr = fmt.Errorf("failed to initialize AKS provider: %w", err)
				return
			}
			registry.Register(o11yv1.CloudProviderAKS, aksProvider)
		case o11yv1.CloudProviderACK:
			ackProvider, err := NewACKCloudProvider(region)
			if err != nil {
				cloudProviderRegistryErr = fmt.Errorf("failed to initialize ACK provider: %w", err)
				return
			}
			registry.Register(o11yv1.CloudProviderACK, ackProvider)
		}
		cloudProviderRegistryGlobal = registry
	})

	if cloudProviderRegistryErr != nil {
		return nil, cloudProviderRegistryErr
	}
	if cloudProviderRegistryRegion != region {
		return nil, fmt.Errorf("cloud provider registry already initialized with region %q (got %q)", cloudProviderRegistryRegion, region)
	}
	return cloudProviderRegistryGlobal, nil
}

// Get retrieves the cloud provider implementation for the given provider
func (r *CloudProviderRegistry) Get(provider o11yv1.CloudProvider) (CloudProviderInterface, error) {
	if p, ok := r.providers[provider]; ok {
		return p, nil
	}
	return nil, fmt.Errorf("unsupported cloud provider: %s", provider)
}

// Register registers a cloud provider implementation
func (r *CloudProviderRegistry) Register(provider o11yv1.CloudProvider, impl CloudProviderInterface) {
	r.providers[provider] = impl
}

// NewCloudProviderRegistryForTesting creates a registry with the given providers (for testing only).
func NewCloudProviderRegistryForTesting(providerMap map[o11yv1.CloudProvider]CloudProviderInterface) *CloudProviderRegistry {
	if providerMap == nil {
		providerMap = make(map[o11yv1.CloudProvider]CloudProviderInterface)
	}
	return &CloudProviderRegistry{providers: providerMap}
}

// RegisterCloudProviderMock registers a mock provider for testing. Uses default region "us-west-2" when
// initializing the global registry. Returns a cleanup function to unregister the mock.
func RegisterCloudProviderMock(provider o11yv1.CloudProvider, impl CloudProviderInterface) func() {
	if cloudProviderRegistryGlobal == nil {
		_, err := NewCloudProviderRegistry(provider, "us-west-2", nil)
		if err != nil {
			panic(err)
		}
	}
	cloudProviderRegistryGlobal.Register(provider, impl)
	return func() {
		cloudProviderRegistryGlobal.Register(provider, nil)
	}
}
