package k8s

import (
	"context"
	"encoding/json"
	"net/url"
	"os"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"
	sdkresource "github.com/pingcap/test-infra/sdk/resource"
)

var (
	_ sdkresource.Client = LoadBalancerClient{}
)

type LoadBalancerClient struct {
	*KubeClientSet
}

func NewLoadBalancerClient(k *KubeClientSet) LoadBalancerClient {
	return LoadBalancerClient{k}
}

// Create creates resource
func (l LoadBalancerClient) Create(spec json.RawMessage) (core.Resource, error) {
	service, resHandle, err := l.buildLoadBalancerObject(spec)
	if err != nil {
		return nil, err
	}

	service, err = l.ClientSet.CoreV1().Services(l.Namespace).Create(context.TODO(), service, metav1.CreateOptions{})
	if err != nil {
		return nil, err
	}

	resHandle.Provider = l.FQDN()
	resHandle.Testbed = l.Namespace
	resHandle.Ready = &[]bool{true}[0]
	return &LoadBalancer{LoadBalancer: *resHandle, KubeClientSet: l.KubeClientSet, service: service}, nil
}

func (l LoadBalancerClient) buildLoadBalancerObject(param json.RawMessage) (*corev1.Service, *apiresource.LoadBalancer, error) {
	resHandle := &apiresource.LoadBalancer{}
	if err := json.Unmarshal(param, resHandle); err != nil {
		return nil, nil, err
	}

	owner, err := GetOwnerReferenceFromTestbed(l.KubeClientSet, resHandle.Testbed)
	if err != nil {
		return nil, nil, err
	}

	selector := map[string]string{"app.kubernetes.io/managed-by": "test-infra"}
	for k, v := range resHandle.Spec.Selector {
		selector[k] = v
	}

	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      resHandle.Name,
			Namespace: l.Namespace,
			Labels: map[string]string{
				"app.kubernetes.io/managed-by": "test-infra",
				TestInfraName:                  resHandle.Name,
			},
			OwnerReferences: []metav1.OwnerReference{*owner},
		},
		// if ClusterIP is `None`, Type must set to `ClusterIP`, else no need to set `ClusterIP`
		Spec: corev1.ServiceSpec{
			Type:     corev1.ServiceTypeClusterIP,
			Selector: selector,
			Ports: []corev1.ServicePort{
				{
					Name: resHandle.Name,
					Port: resHandle.Spec.Port,
				},
			},
		},
	}

	return service, resHandle, nil
}

// Get gets resource
func (l LoadBalancerClient) Get(handle apiresource.ResourceHandle) (core.Resource, error) {
	loadbalancer := &LoadBalancer{
		KubeClientSet: l.KubeClientSet,
		LoadBalancer:  apiresource.LoadBalancer{ResourceHandle: handle},
	}

	loadbalancer.Provider = l.FQDN()
	if err := loadbalancer.Sync(); err != nil {
		return nil, err
	}
	return loadbalancer, nil
}

// GetEndpoints gets Endpoints of a resource
func (l LoadBalancerClient) GetEndpoints(handle apiresource.ResourceHandle) (apiresource.ResourceEndpoints, error) {
	return apiresource.ResourceEndpoints{}, nil
}

// Delete deletes resources
func (l LoadBalancerClient) Delete(handle apiresource.ResourceHandle) error {
	return l.ClientSet.CoreV1().Services(l.Namespace).Delete(context.TODO(), handle.Name, metav1.DeleteOptions{})
}

// ReserveEnv reserves environment without the loss of debugging on resource
func (l LoadBalancerClient) ReserveEnv(handle apiresource.ResourceHandle, action apiresource.GcReserveEnvAction) error {
	return nil
}

// Artifact archives logs for debugging on resource
func (l LoadBalancerClient) Artifact(handle apiresource.ResourceHandle, action apiresource.GcArtifactAction, archiver resource.ArchiverFunc) (map[string]*url.URL, error) {
	return nil, nil
}

// FQDN return the fully qualified domain name of the Client
func (l LoadBalancerClient) FQDN() string {
	return resource.FQDN(l)
}

type LoadBalancerGetter struct{ handle apiresource.ResourceHandle }

func (t *LoadBalancerGetter) Get() (core.Resource, error) {
	k, err := NewKubeClientFromKubeConfig(t.handle.Testbed, os.Getenv("KUBECONFIG"))
	if err != nil {
		return nil, err
	}
	return NewLoadBalancerClient(k).Get(t.handle)
}

func init() {
	core.RegisterProvider(sdkresource.FQDN(LoadBalancerClient{}),
		func(handle apiresource.ResourceHandle) core.ResourceGetter {
			return &LoadBalancerGetter{handle: handle}
		})
}
