package cluster

import (
	"context"
	"fmt"
	"net/http"

	"github.com/Masterminds/semver"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/controllers/utils/configmanager"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

var (
	eksVolumeAttributesClassConstraint, _ = semver.NewConstraint(">= 1.31")
	gkeVolumeAttributesClassConstraint, _ = semver.NewConstraint(">= 1.34-0")
)

type DataplaneHTTPEndpoint interface {
	// GetProxyURL gets clusterproxy url or dataplane kong gateway url
	GetProxyURL(context.Context, string) (string, error)
	GetPDClient(context.Context, *http.Client, configmanager.PDAuth, string, bool) (configmanager.DynamicPDClient, error)
}

type proxyClient struct {
	kubeCli    client.Client
	serviceKey types.NamespacedName
}

// NewServiceHTTPEndpoint build a DataplaneHTTPEndpoint by retrieving dataplane proxy service
func NewServiceHTTPEndpoint(kubeCli client.Client, serviceKey types.NamespacedName) DataplaneHTTPEndpoint {
	return &proxyClient{
		kubeCli:    kubeCli,
		serviceKey: serviceKey,
	}
}

func (p *proxyClient) GetProxyURL(ctx context.Context, clusterName string) (string, error) {
	var svc corev1.Service
	if err := p.kubeCli.Get(ctx, p.serviceKey, &svc); err != nil {
		return "", err
	}
	ings := svc.Status.LoadBalancer.Ingress
	if len(ings) < 1 {
		return "", fmt.Errorf("proxy endpoint is not ready")
	}
	// aws uses `.Status.LoadBalancer.Ingress.HostName` to record the lb address.
	proxyEp := ings[0].Hostname
	if proxyEp == "" {
		// gcp uses `.Status.LoadBalancer.Ingress.IP`
		proxyEp = ings[0].IP
	}
	return fmt.Sprintf("http://%s/proxy/cluster/%s", proxyEp, clusterName), nil
}

func (p *proxyClient) GetPDClient(ctx context.Context,
	httpCli *http.Client,
	pdAuth configmanager.PDAuth,
	clusterName string,
	dryRun bool,
) (configmanager.DynamicPDClient, error) {
	url, err := p.GetProxyURL(ctx, clusterName)
	if err != nil {
		return nil, err
	}
	pdOpts := []configmanager.Option{
		configmanager.HttpClient(httpCli),
		configmanager.WithDryrun(dryRun),
	}
	if pdAuth != nil {
		pdOpts = append(pdOpts, configmanager.Auth(pdAuth))
	}
	return configmanager.NewDefaultPDClient(url+"/pd", pdOpts...), nil
}

type endpointClient struct {
	baseURL string
}

// Build DataplaneHTTPEndpoint from proxy base url
// region-server should pass dataplane proxy url to cluster-service
func NewURLHTTPEndpoint(baseURL string) DataplaneHTTPEndpoint {
	return &endpointClient{
		baseURL: baseURL,
	}
}

func (p *endpointClient) GetProxyURL(ctx context.Context, clusterName string) (string, error) {
	return fmt.Sprintf("%s/proxy/cluster/%s", p.baseURL, clusterName), nil
}

func (p *endpointClient) GetPDClient(ctx context.Context,
	httpCli *http.Client,
	pdAuth configmanager.PDAuth,
	clusterName string,
	dryRun bool,
) (configmanager.DynamicPDClient, error) {
	url, err := p.GetProxyURL(ctx, clusterName)
	if err != nil {
		return nil, err
	}
	pdOpts := []configmanager.Option{
		configmanager.HttpClient(httpCli),
		configmanager.WithDryrun(dryRun),
	}
	if pdAuth != nil {
		pdOpts = append(pdOpts, configmanager.Auth(pdAuth))
	}
	return configmanager.NewDefaultPDClient(url+"/pd", pdOpts...), nil
}

func supportsVolumeAttributesClass(provider, version string, usesTiDBOperatorV2 bool) bool {
	var constraint *semver.Constraints
	switch provider {
	case cloudv1.ProviderTypeEKS:
		constraint = eksVolumeAttributesClassConstraint
	case cloudv1.ProviderTypeGKE:
		if usesTiDBOperatorV2 {
			return false
		}
		constraint = gkeVolumeAttributesClassConstraint
	default:
		return false
	}
	return supportsVersion(version, constraint)
}

func supportsVersion(version string, constraint *semver.Constraints) bool {
	v, err := semver.NewVersion(version)
	return err == nil && constraint.Check(v)
}
